2017-01-14 8 views
3

속성으로 선언 된 개인 직렬 큐가 있는데 아주 이상한 상황이 있습니다.dispatch_queue_t 속성에 getter를 호출하면 충돌이 발생합니다.

속성을 dispatch_async하면 충돌합니다 (EXC_BAD_ACCESS (code = EXC_i386_GPFLT)). 디버깅을 한 후에 getter가 호출 되었기 때문에 디버깅이 필요하다는 것을 알았습니다. getter가 호출되지 않으면 충돌이 발생하지 않습니다. 또한 self.queue가 호출 된 두 번째 시간은 항상 충돌합니다. 아래의 두 번째 예를 참조하십시오.

처음으로 합성 된 getter 호출이 어떻게 든 ivar이 과도하게 릴리스 된 것처럼 보입니다.

iOS 9 이상을 타겟팅하므로 OS_OBJECT_USE_OBJC을 (를) 확인하지 않습니다.

예 1)이 작동하지 않습니다

@interface Test() 
@property (nonatomic, strong) dispatch_queue_t initQueue; 
@end 

- (instancetype)init { 
    self = [super init]; 
    if (self) { 
     _initQueue = dispatch_queue_create("com.test.initQueue", DISPATCH_QUEUE_SERIAL); 
    } 
    return self; 
} 

- (void)onCompletion:(void (^)())completion { 
    // Crashes here - EXC_BAD_ACCESS (code=EXC_i386_GPFLT) 
    // the second time self.queue is accessed - either by subsequent call into 
    // this method, or by adding NSLog(@"%@", self.queue) before this line. 
    dispatch_async(self.initQueue, ^{ 
    ... 
    }); 
} 

예 2) 이것은 또한 작동하지 않습니다

@interface Test() 
@property (nonatomic, strong) dispatch_queue_t initQueue; 
@end 

- (instancetype)init { 
    self = [super init]; 
    if (self) { 
     _initQueue = dispatch_queue_create("com.test.initQueue", DISPATCH_QUEUE_SERIAL); 
    } 
    return self; 
} 

- (void)onCompletion:(void (^)())completion { 
    NSLog(@"%@", self.initQueue); 
    // Crashes below - EXC_BAD_INSTRUCTION (code=EXC_i386_INVOP, subcode=0x0) 
    NSLog(@"%@", self.initQueue); 
} 

예 3) 그것은 작동 내가 게터를 사용하여 멀리있는 경우 :

@interface Test() 
@property (nonatomic, strong) dispatch_queue_t initQueue; 
@end 

- (instancetype)init { 
     self = [super init]; 
     if (self) { 
      _initQueue = dispatch_queue_create("com.test.initQueue", DISPATCH_QUEUE_SERIAL); 
     } 
     return self; 
    } 

- (void)onCompletion:(void (^)())completion { 
    // Works fine 
    dispatch_async(_initQueue, ^{ 
    ... 
    }); 
} 

예 4) 나는 게터 공급하는 경우에도 작동합니다

012,351을
@interface Test() 
@property (nonatomic, strong) dispatch_queue_t initQueue; 
@end 

- (instancetype)init { 
     self = [super init]; 
     if (self) { 
      _initQueue = dispatch_queue_create("com.test.initQueue", DISPATCH_QUEUE_SERIAL); 
     } 
     return self; 
    } 

- (dispatch_queue_t)initQueue { 
    return _initQueue; 
} 

- (void)onCompletion:(void (^)())completion { 
    // Works fine 
    dispatch_async(self.initQueue, ^{ 
    ... 
    }); 
} 

예 5) 속성 대신 대기열에 ivar를 사용하거나 self.initQueue에 기본 대기열이 할당 된 경우에도 작동합니다.

이 동작의 이유는 무엇입니까?

다른 오픈 소스 라이브러리는 getter와 함께 dispatch_queue_t에 대한 속성을 사용하고 있으며 전혀 문제가 없습니다. 예 : 원래 재산 initQueue라는 이름의 코멘트를 https://github.com/rs/SDWebImage/blob/7e0964f8d90dcd80d535c52dd9f6d5fa7432052b/SDWebImage/SDImageCache.m#L57

+0

당신이 말한 것처럼 그것을 사용하여 라이브러리의 링크를 제공 할 수 있습니까? 왜 그 게터를 공급할 수 없습니까? –

+0

예제 코드는 내 자신의 코드입니다. 비슷한 코드를 사용하는 다른 라이브러리를 보았지만 내 문제는 발생하지 않았습니다. – Boon

+0

다음은 잘 동작하는 기존 코드 예제입니다. https://github.com/rs/SDWebImage/blob/7e0964f8d90dcd80d535c52dd9f6d5fa7432052b/SDWebImage/SDImageCache.m#L57 – Boon

답변

3

이 차례로 the ARC Method family 규칙의 충돌한다 떨어졌다 initQueue라는 방법을 만들었습니다. 이러한 규칙은 ARC가 new 또는 init으로 시작하는 방법에 NS_RETURNS_RETAINED으로 자동 주석을 붙일 것임을 나타냅니다.

init 패밀리의 메서드는 암시 적으로 self 매개 변수를 사용하고 유지 된 개체를 반환합니다. 속성을 통해 이러한 속성 중 어느 것도 변경할 수 없습니다.

이 차례로 메서드의 호출자는 반환 된 값의 소유권을 가져오고 보유 값을 증가시킬 필요가 없다고 가정하는 것이 안전하다는 것을 의미합니다. 결과적으로 ARC 속성을 사용하려고 시도했을 때 참조 횟수가 증가하지는 않았지만 ARC는 메소드가 끝날 때 릴리스 호출을 남겼습니다. 그 결과 귀하의 클래스가 dealloc 일 때 이전에 부동산 가치가 공개되었습니다.

어떤 경우에는 속성을 사용하여이 동작을 무시할 수 있습니다. 그러나 그들은 단지 메소드를 인식하고있는 것이 좋습니다. 특히 메소드에 대한 성능에 큰 영향을 줄 수 있습니다.

다른 함정이 알아야 할 다음 alloc, copy, mutableCopynew 가족

방법 - 즉, init를 제외한 모든 현재 정의 된 가족의 방법 - 암시 적으로 유지 개체를 반환 주석이 ns_returns_retained 속성으로 주석 된 경우. 이는 ns_returns_autoreleased 또는 ns_returns_not_retained 속성 중 하나를 사용하여 메소드에 주석을 달아 재정의 할 수 있습니다.

물론 이것에 측면 참고

는 프로그램마다 init 메소드 호출에서 수행 할 수 있다는 것을 제외하고는 동일한 개체에 init 방법으로 두 개 이상의 전화를 야기하는 정의 동작은 대부분의 대표는 init입니다.

슬프게도 컴파일러는 경고하지 않습니다.

+0

호기심, 왜 self.initQueue에 dispatch_get_main_queue가 할당 된 경우이 작업이 가능할까요? 어쩌면 여기서 이름 짓기가 유일한 문제는 아닙니다. – Boon

+1

@Boon 메인 큐를 공개 할 수 없기 때문에 Bayer이 그것을 막을 수 있기 때문에 'release'를 명시 적으로 구현할 수도 있습니다. – Mgetz

+0

@Boon - BTW,이 이름이 루트라고 믿지 않으면 문제는 어셈블러 코드를 살펴보면 다른 변수에 대해 생성 된 메모리 처리가 다르다는 증거를 볼 수 있다는 것입니다. 조금씩 따라하기가 어렵지만 생성 된 코드 ARC가 다른 것을 분명하게 볼 수 있습니다. – Rob