dispatch_block 崩溃
Crash with dispatch_block
我一直在努力了解这次崩溃背后的原因,以便更多地了解块的行为方式。我有一个非常简单的 class 来触发这次崩溃。
@implementation BlockCrashTest
- (void)doSomething
{
dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_block_t block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_group_t group = dispatch_group_create();
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
dispatch_group_enter(group);
[strongSelf performSomethingAsync:^{
dispatch_group_leave(group);
}];
if(dispatch_group_wait(group, time) != 0) {
NSLog(@"group already finished");
}
};
dispatch_async(queue, block);
}
- (void)performSomethingAsync:(void(^)(void))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(5);
completion();
});
}
- (void)dealloc
{
NSLog(@"released object");
}
@end
现在,如果我分配 class 并简单地调用方法 doSomething 给它,
BlockCrashTest *someObject = [[BlockCrashTest alloc] init];
[someObject doSomething];
它崩溃了异常,EXC_BAD_INSTRUCTION 和以下堆栈跟踪,
#0 0x000000011201119a in _dispatch_semaphore_dispose ()
#1 0x0000000112013076 in _dispatch_dispose ()
#2 0x0000000112026172 in -[OS_dispatch_object _xref_dispose] ()
#3 0x000000010ef4c2fd in __29-[BlockCrashTest doSomething]_block_invoke at /Users/Sandeep/Desktop/Test Block Crash/Test Block Crash/ViewController.m:35
#4 0x0000000112005ef9 in _dispatch_call_block_and_release ()
如果我修改方法 doSomething,使其不使用 weak 而使用 self,则不会发生崩溃并且方法似乎按预期执行,
- (void)doSomething
{
dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = ^{
dispatch_group_t group = dispatch_group_create();
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
dispatch_group_enter(group);
[self performSomethingAsync:^{
dispatch_group_leave(group);
}];
if(dispatch_group_wait(group, time) != 0) {
NSLog(@"group already finished");
}
};
dispatch_async(queue, block);
}
为什么会崩溃,我的理解是在块内部使用 weak 将确保不会调用该方法,如果对象被释放我认为 weak比在块内使用 self 更安全。
如果我保留对象 BlockCrashTest 并调用它的方法,上面的代码与 weakSelf 一起工作得很好。
如果有人能解释崩溃背后的原因以及这 3 种不同的代码变体到底发生了什么,一个崩溃和其他似乎工作正常,我将非常高兴。
Note: This is in one way or other related to the crash listed in
thread,
Objective-C crash on __destroy_helper_block_.
I have been able to reproduce the exact same stack traces with my code
above.
即使您删除 self performSomethingAsync:
的调用,您也会遇到同样的崩溃。此崩溃由 libdispatch
信号量 API 引起。您可以在 Xcode 中看到崩溃函数 _dispatch_semaphore_dispose
的汇编跟踪:
如果我们试图弄清楚这段代码中发生了什么,我们会看到您明确地标记当前块进入组呼叫 dispatch_group_enter
。然后 performSomethingAsync
没有调用,因为 strongSelf == nil
。这意味着 dispatch_group_enter
与 dispatch_group_leave
不平衡,这导致该组无法正确处理并崩溃(参见 asm 清单)。
如果你使用 self
这段代码也会崩溃,因为 dispatch_group_leave(group);
从不同的线程调用 dispatch_group_enter
这也会导致同样的崩溃,但从另一个角度来看:调用不平衡同一个线程。 performSomethingAsync
在不同的队列中调用了完成块,而不是在你的队列中 "com.queue.test"
。
这个例子只是错误地使用了 dispatch_groups
APIs。要了解如何正确使用它,请参阅 apple doc。
一些观察:
当 dispatch_group_t
对象被释放时,你不能有不平衡 "enter" 和 "leave" 的调度组。正如 ilya 指出的那样,由于你的模式,strongSelf
是 nil
,所以你进入了这个组,但没有离开它。
weakSelf
和 strongSelf
舞蹈中一个非常常见的模式是只检查 strongSelf
是否是 nil
,解决不平衡问题。因此,如果 strongSelf
是 nil
,它会完全绕过调度组的东西,但如果不是 nil
,"enter" 和 "leave" 都会被调用:
- (void)doSomething {
dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL);
typeof(self) weakSelf = self;
dispatch_async(queue, ^{
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[strongSelf performSomethingAsync:^{
dispatch_group_leave(group);
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}
});
}
显然,您必须确保 performSomethingAsync
方法本身,总是 调用离开组的块。
解决此问题的另一种方法(如果您不能保证所有 "enter" 和 "leave" 将保持平衡),是使用信号量:
- (void)doSomething {
dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL);
typeof(self) weakSelf = self;
dispatch_async(queue, ^{
typeof(self) strongSelf = weakSelf;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[strongSelf performSomethingAsync:^{
dispatch_semaphore_signal(semaphore);
}];
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, time) != 0) {
NSLog(@"semaphore not received in time");
}
});
}
坦率地说,即使像我上面那样使用信号量,我仍然认为人们会想要检查以确认 strongSelf
不是 nil
。如果不将消息添加到导致无操作的 nil
对象的可能性,并发编程就已经足够混乱了。
- (void)doSomething {
dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL);
typeof(self) weakSelf = self;
dispatch_async(queue, ^{
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[strongSelf performSomethingAsync:^{
dispatch_semaphore_signal(semaphore);
}];
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, time) != 0) {
NSLog(@"semaphore not received in time");
}
}
});
}
MacOS 10.8 和 iOS6.0 引入了 ARC 来分派对象。来自文档 GCD Objects and Automatic Reference Counting:
When you build your app using the Objective-C compiler, all dispatch objects are Objective-C objects. As such, when automatic reference counting (ARC) is enabled, dispatch objects are retained and released automatically just like any other Objective-C object. When ARC is not enabled, use the dispatch_retain and dispatch_release functions (or Objective-C semantics) to retain and release your dispatch objects. You cannot use the Core Foundation retain/release functions.
If you need to use retain/release semantics in an ARC-enabled app with a later deployment target (for maintaining compatibility with existing code), you can disable Objective-C-based dispatch objects by adding -DOS_OBJECT_USE_OBJC=0 to your compiler flags.
在您的情况下,ARC 正在愉快地管理您的 dispatch_group_t
的生命周期。而且,不幸的是,您的代码导致组在锁仍在等待时被释放。当组超时时它被释放 - 所以当调用 dispatch_group_leave
时它崩溃,因为组已经被释放。
我建议至少在尝试离开之前检查该组是否为 NULL。
此外,您的等待结果逻辑是相反的。零结果表示该组在超时前被清空,非零结果表示超时。
我一直在努力了解这次崩溃背后的原因,以便更多地了解块的行为方式。我有一个非常简单的 class 来触发这次崩溃。
@implementation BlockCrashTest
- (void)doSomething
{
dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_block_t block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_group_t group = dispatch_group_create();
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
dispatch_group_enter(group);
[strongSelf performSomethingAsync:^{
dispatch_group_leave(group);
}];
if(dispatch_group_wait(group, time) != 0) {
NSLog(@"group already finished");
}
};
dispatch_async(queue, block);
}
- (void)performSomethingAsync:(void(^)(void))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(5);
completion();
});
}
- (void)dealloc
{
NSLog(@"released object");
}
@end
现在,如果我分配 class 并简单地调用方法 doSomething 给它,
BlockCrashTest *someObject = [[BlockCrashTest alloc] init];
[someObject doSomething];
它崩溃了异常,EXC_BAD_INSTRUCTION 和以下堆栈跟踪,
#0 0x000000011201119a in _dispatch_semaphore_dispose ()
#1 0x0000000112013076 in _dispatch_dispose ()
#2 0x0000000112026172 in -[OS_dispatch_object _xref_dispose] ()
#3 0x000000010ef4c2fd in __29-[BlockCrashTest doSomething]_block_invoke at /Users/Sandeep/Desktop/Test Block Crash/Test Block Crash/ViewController.m:35
#4 0x0000000112005ef9 in _dispatch_call_block_and_release ()
如果我修改方法 doSomething,使其不使用 weak 而使用 self,则不会发生崩溃并且方法似乎按预期执行,
- (void)doSomething
{
dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = ^{
dispatch_group_t group = dispatch_group_create();
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
dispatch_group_enter(group);
[self performSomethingAsync:^{
dispatch_group_leave(group);
}];
if(dispatch_group_wait(group, time) != 0) {
NSLog(@"group already finished");
}
};
dispatch_async(queue, block);
}
为什么会崩溃,我的理解是在块内部使用 weak 将确保不会调用该方法,如果对象被释放我认为 weak比在块内使用 self 更安全。
如果我保留对象 BlockCrashTest 并调用它的方法,上面的代码与 weakSelf 一起工作得很好。
如果有人能解释崩溃背后的原因以及这 3 种不同的代码变体到底发生了什么,一个崩溃和其他似乎工作正常,我将非常高兴。
Note: This is in one way or other related to the crash listed in thread, Objective-C crash on __destroy_helper_block_. I have been able to reproduce the exact same stack traces with my code above.
即使您删除 self performSomethingAsync:
的调用,您也会遇到同样的崩溃。此崩溃由 libdispatch
信号量 API 引起。您可以在 Xcode 中看到崩溃函数 _dispatch_semaphore_dispose
的汇编跟踪:
如果我们试图弄清楚这段代码中发生了什么,我们会看到您明确地标记当前块进入组呼叫 dispatch_group_enter
。然后 performSomethingAsync
没有调用,因为 strongSelf == nil
。这意味着 dispatch_group_enter
与 dispatch_group_leave
不平衡,这导致该组无法正确处理并崩溃(参见 asm 清单)。
如果你使用 self
这段代码也会崩溃,因为 dispatch_group_leave(group);
从不同的线程调用 dispatch_group_enter
这也会导致同样的崩溃,但从另一个角度来看:调用不平衡同一个线程。 performSomethingAsync
在不同的队列中调用了完成块,而不是在你的队列中 "com.queue.test"
。
这个例子只是错误地使用了 dispatch_groups
APIs。要了解如何正确使用它,请参阅 apple doc。
一些观察:
当
dispatch_group_t
对象被释放时,你不能有不平衡 "enter" 和 "leave" 的调度组。正如 ilya 指出的那样,由于你的模式,strongSelf
是nil
,所以你进入了这个组,但没有离开它。weakSelf
和strongSelf
舞蹈中一个非常常见的模式是只检查strongSelf
是否是nil
,解决不平衡问题。因此,如果strongSelf
是nil
,它会完全绕过调度组的东西,但如果不是nil
,"enter" 和 "leave" 都会被调用:- (void)doSomething { dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); typeof(self) weakSelf = self; dispatch_async(queue, ^{ typeof(self) strongSelf = weakSelf; if (strongSelf) { dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); [strongSelf performSomethingAsync:^{ dispatch_group_leave(group); }]; dispatch_group_wait(group, DISPATCH_TIME_FOREVER); } }); }
显然,您必须确保
performSomethingAsync
方法本身,总是 调用离开组的块。解决此问题的另一种方法(如果您不能保证所有 "enter" 和 "leave" 将保持平衡),是使用信号量:
- (void)doSomething { dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); typeof(self) weakSelf = self; dispatch_async(queue, ^{ typeof(self) strongSelf = weakSelf; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [strongSelf performSomethingAsync:^{ dispatch_semaphore_signal(semaphore); }]; dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); if (dispatch_semaphore_wait(semaphore, time) != 0) { NSLog(@"semaphore not received in time"); } }); }
坦率地说,即使像我上面那样使用信号量,我仍然认为人们会想要检查以确认
strongSelf
不是nil
。如果不将消息添加到导致无操作的nil
对象的可能性,并发编程就已经足够混乱了。- (void)doSomething { dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); typeof(self) weakSelf = self; dispatch_async(queue, ^{ typeof(self) strongSelf = weakSelf; if (strongSelf) { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [strongSelf performSomethingAsync:^{ dispatch_semaphore_signal(semaphore); }]; dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); if (dispatch_semaphore_wait(semaphore, time) != 0) { NSLog(@"semaphore not received in time"); } } }); }
MacOS 10.8 和 iOS6.0 引入了 ARC 来分派对象。来自文档 GCD Objects and Automatic Reference Counting:
When you build your app using the Objective-C compiler, all dispatch objects are Objective-C objects. As such, when automatic reference counting (ARC) is enabled, dispatch objects are retained and released automatically just like any other Objective-C object. When ARC is not enabled, use the dispatch_retain and dispatch_release functions (or Objective-C semantics) to retain and release your dispatch objects. You cannot use the Core Foundation retain/release functions.
If you need to use retain/release semantics in an ARC-enabled app with a later deployment target (for maintaining compatibility with existing code), you can disable Objective-C-based dispatch objects by adding -DOS_OBJECT_USE_OBJC=0 to your compiler flags.
在您的情况下,ARC 正在愉快地管理您的 dispatch_group_t
的生命周期。而且,不幸的是,您的代码导致组在锁仍在等待时被释放。当组超时时它被释放 - 所以当调用 dispatch_group_leave
时它崩溃,因为组已经被释放。
我建议至少在尝试离开之前检查该组是否为 NULL。
此外,您的等待结果逻辑是相反的。零结果表示该组在超时前被清空,非零结果表示超时。