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

标签 objective-c ios9 grand-central-dispatch

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

如果我 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)如果我不使用 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;
    }

- (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

最佳答案

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

Methods in the init family implicitly consume their self parameter and return a retained object. Neither of these properties can be altered through attributes.

这反过来意味着该方法的调用者应该可以安全地假设他们正在获取返回值的所有权并且不需要增加保留值。因此,当您尝试使用该属性时,ARC 并没有像预期的那样增加引用计数,但 ARC 仍然在该方法的末尾留下了一个释放调用。这导致您的属性值在您的类被dealloced 之前被释放。

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

其他需要注意的陷阱:

Methods in the alloc, copy, mutableCopy, and new families — that is, methods in all the currently-defined families except init — implicitly return a retained object as if they were annotated with the ns_returns_retained attribute. This can be overridden by annotating the method with either of the ns_returns_autoreleased or ns_returns_not_retained attributes.

对此还有一个旁注:

It is undefined behavior for a program to cause two or more calls to init methods on the same object, except that each init method invocation may perform at most one delegate init call.

遗憾的是,编译器似乎没有就此发出警告。

关于objective-c - 为 dispatch_queue_t 属性调用 getter 会导致崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41653045/

相关文章:

swift - 无法使用 Swift 更新 Parse 中的图像文件列

mysql - 我怎样才能提高我的 ios 应用程序的速度?

ios - 在 Swift 2 中,如何将 JSON 数组转换为 [Int32]

ios - 通知 iOS 应用程序更新其内容

thread-safety - GCD 是否保证在同一个队列中工作的所有 block 总是在同一个线程中工作?(关于 ABAddressBookRef)

ios - 什么 NSDateFormat 是 1991-07-26T05 :45:50. 163

objective-c - 不要在 UIBezierPath 之外显示 subview

ios - 动态改变UILabel的字体大小

objective-c - Xcode 分布式构建失败

ios - 我们可以让 iOS 应用向后兼容吗