我有一个声明为属性的私有(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 会自动将任何以 new
或 init
开头的方法注释为 NS_RETURNS_RETAINED
。
Methods in the
init
family implicitly consume theirself
parameter and return a retained object. Neither of these properties can be altered through attributes.
这反过来意味着该方法的调用者应该可以安全地假设他们正在获取返回值的所有权并且不需要增加保留值。因此,当您尝试使用该属性时,ARC 并没有像预期的那样增加引用计数,但 ARC 仍然在该方法的末尾留下了一个释放调用。这导致您的属性值在您的类被dealloc
ed 之前被释放。
在某些情况下,可以使用属性覆盖此行为。但是,我建议只了解方法系列,因为它们可以对您的应用程序产生很好的性能影响,尤其是对于工厂方法。
其他需要注意的陷阱:
Methods in the
alloc
,copy
,mutableCopy
, andnew
families — that is, methods in all the currently-defined families exceptinit
— implicitly return a retained object as if they were annotated with thens_returns_retained
attribute. This can be overridden by annotating the method with either of thens_returns_autoreleased
orns_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 eachinit
method invocation may perform at most one delegateinit
call.
遗憾的是,编译器似乎没有就此发出警告。
关于objective-c - 为 dispatch_queue_t 属性调用 getter 会导致崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41653045/