为dispatch_queue_t属性调用getter会导致崩溃

时间:2017-01-14 17:44:36

标签: objective-c ios9 grand-central-dispatch

我有一个声明为属性的私有串行队列,我遇到了一个非常奇怪的情况。

如果我dispatch_async属性,它将崩溃(EXC_BAD_ACCESS(代码= 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)如果我提供了getter,它也可以工作:

@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分配给主队列,它也会工作。

这种行为的原因是什么?

其他开源库正在使用dispatch_queue_t的属性和getter,它们完全没有问题。示例:https://github.com/rs/SDWebImage/blob/7e0964f8d90dcd80d535c52dd9f6d5fa7432052b/SDWebImage/SDImageCache.m#L57

1 个答案:

答案 0 :(得分:3)

根据您的评论,您最初将属性命名为initQueue,这反过来又创建了一个名为initQueue的方法,该方法违反了the ARC Method family规则。这些规则表明ARC会自动注释以newinit开头的任何方法NS_RETURNS_RETAINED

  

init系列中的方法隐式使用其self参数并返回保留的对象。这些属性都不能通过属性进行更改。

这反过来意味着该方法的调用者应该可以安全地假设他们拥有返回值的所有权并且不需要增加保留值。因此,当您尝试使用该属性时,ARC没有像预期的那样增加引用计数,但ARC仍然在方法结束时留下了一个释放调用。这导致您的媒体资源价值在您的课程dealloc编辑之前发布。

在某些情况下,可以使用属性覆盖此行为。但是,我建议只是了解方法系列,因为它们可以对您的应用程序产生很好的性能影响,特别是对于工厂方法。

要注意的其他陷阱:

  

alloccopymutableCopynew系列中的方法 - 即除init之外的所有当前定义的系列中的方法 - 隐式返回一个保留对象,就像它们使用ns_returns_retained属性进行注释一样。这可以通过使用ns_returns_autoreleasedns_returns_not_retained属性之一注释方法来覆盖。

关于这一点的附注:

  

程序在同一对象上对init方法进行两次或多次调用是未定义的行为,除了每个init方法调用最多只能执行一次委托init调用

可悲的是,编译器似乎没有警告过那个。