为 dispatch_queue_t 属性 调用 getter 导致崩溃
Calling getter for dispatch_queue_t property causes crash
我有一个声明为 属性 的私有串行队列,我 运行 陷入了一个非常奇怪的境地。
如果我 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 their self
parameter and return a retained object. Neither of these properties can be altered through attributes.
这反过来意味着该方法的调用者应该可以安全地假设他们正在取得返回值的所有权并且不需要增加保留值。因此,当您尝试使用 属性 时,ARC 并未按预期增加引用计数,但 ARC 仍会在方法末尾留下释放调用。这导致您的 属性 值在您的 class 被 dealloc
编辑之前被释放。
在某些情况下可以使用属性覆盖此行为。但是我建议只了解方法族,因为它们可以对您的应用程序产生很好的性能影响,尤其是对于工厂方法。
其他需要注意的陷阱:
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.
遗憾的是,编译器似乎没有就此发出警告。
我有一个声明为 属性 的私有串行队列,我 运行 陷入了一个非常奇怪的境地。
如果我 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 仍会在方法末尾留下释放调用。这导致您的 属性 值在您的 class 被 dealloc
编辑之前被释放。
在某些情况下可以使用属性覆盖此行为。但是我建议只了解方法族,因为它们可以对您的应用程序产生很好的性能影响,尤其是对于工厂方法。
其他需要注意的陷阱:
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.
遗憾的是,编译器似乎没有就此发出警告。