Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save linearhw/77afec616d79d51b1be31fb173c49b15 to your computer and use it in GitHub Desktop.
Save linearhw/77afec616d79d51b1be31fb173c49b15 to your computer and use it in GitHub Desktop.
Effective Objective-C 46

사실 이미 iOS 6.0 에서 deprecated 이다.

이 메서드를 흔히 사용하는 패턴

  • 데드락을 막기 위해서
- (NSString*)someString {
    __block NSString *localSomeString;
    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
    return localSomeString;
}

- (void)setSomeString:(NSString*)someString {
    dispatch_async(_syncQueue, ^{
        _someString = someString;
    });
}

이 경우 syncQueue 에서 someString 의 getter 를 부르면 데드락이 발생한다.
그래서 다음과 같은 코드를 작성하게 된다.

- (NSString*)someString {
    __block NSString *localSomeString;
    dispatch_block_t accessorBlock = ^{
        localSomeString = _someString;
    };

    // 지금 queue 가 syncQueue 이면 sync 쓰지 말고 그냥 block 실행
    if (dispatch_get_current_queue() == _syncQueue) {
        accessorBlock();
    } else {
        dispatch_sync(_syncQueue, accessorBlock);
    }

    return localSomeString;
}

하지만 이 코드는 여전히 위험하고 데드락에 빠질 수 있다.

왜 다시 데드락?

  • 이건 반드시 데드락에 걸리는 코드다.
dispatch_sync(queueA, ^{
    dispatch_sync(queueB, ^{
        dispatch_sync(queueA, ^{
            // Deadlock
        });
    });
});
  • 그럼 이걸 아까의 방법으로 피해갈 수 있을까?
dispatch_sync(queueA, ^{
    dispatch_sync(queueB, ^{
        dispatch_block_t block = ^{ /**/ };
        if (dispatch_get_current_queue() == queueA) {
            block();
        } else {
            dispatch_sync(queueA, block);
        }
    });
});
  • 할 수 없다. 왜냐하면 dispatch_get_current_queue() 는 queueB 를 반환하기 때문.

  • 큐가 계층 구조로 되어 있기 때문에 (queueB -> queueA) 이런 검사는 항상 잘못된 결과를 주게 될 것이다.

  • 이 상황에서의 정답은, 동기화에 사용된 큐에서 절대로 someString getter 를 호출하지 않아야 한다는 것이다.

  • queue 는 매우 가벼운 객체이기 때문에 property 별로 각각 queue 를 만들어 사용해도 괜찮다.

이 문제를 해결하려면?

  • GCD 에 있는 queue-specific data 함수를 사용한다.
    이 함수를 사용하면 임의의 데이터를 큐에 key-value 로 연관시킬 수 있다.
    그리고 특정 키에 연관된 데이터가 없으면 계층 구조를 따라 올라가거나 최상위 계층에 다다를 때까지 찾는다.

??

dispatch_set_target_queue(queueB, queueA);

dispatch_queue_set_specific(queueA, 
                            &kQueueSpecific, // key (void *)
                            (void*)queueSpecificValue, // value
                            (dispatch_function_t)CFRelease);

dispatch_sync(queueB, ^{
    dispatch_block_t block = ^{ NSLog(@"No deadlock!"); };
    
    CFStringRef retrievedValue = dispatch_get_specific(&kQueueSpecific);
    if (retrievedValue) {
        block();
    } else {
        dispatch_sync(queueA, block);
    }
});
  • 이때 key 는 포인터 값으로 비교한다. 그래서 ARC 환경인데 key 에 객체를 사용하면 메모리 관리가 힘들다.
  • 하지만 위의 예처럼 CoreFoundation 객체를 사용하면 ARC가 메모리 관리를 신경쓰지 않기 때문에 편리하다.

dispatch_get_current_queue 가 유용할 때도 있다!

  • 디버깅할 때 (release 에 포함되지 않는다면)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment