我一直试图了解这次崩溃背后的原因,以便更好地了解块的行为方式。我有一个非常简单的类来触发这次崩溃。
@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
现在,如果我分配课程并简单地将方法 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 ,使其不使用弱但使用self,则不会发生崩溃,并且方法似乎按预期执行,< / p>
- (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);
}
为什么会崩溃,我的理解是在块内部使用弱将确保不会调用该方法,如果对象被释放并且我认为弱比使用<更安全在块内强烈>自我。
如果我保留对象 BlockCrashTest 并调用方法,上面的代码可以正常使用 weakSelf 。
如果有人能够解释崩溃背后的原因以及这些3种不同的代码变体在一次崩溃之后究竟发生了什么,我会非常高兴。其他似乎工作正常。
注意:这与其中列出的崩溃有关 线, Objective-C crash on __destroy_helper_block_。 我已经能够使用我的代码重现完全相同的堆栈跟踪 上方。
答案 0 :(得分:3)
有几点意见:
取消分配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
方法本身总是调用离开该组的块。
解决这个问题的另一种方法(如果你没有保证所有的“进入”和“离开”将是平衡的),就是使用信号量:
- (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");
}
}
});
}
答案 1 :(得分:2)
即使您删除了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
API这个例子是错误的。要了解如何正确使用它,请参阅apple doc。
答案 2 :(得分:1)
MacOS 10.8和iOS 6.0引入了ARC来分派对象。来自文档GCD Objects and Automatic Reference Counting:
使用Objective-C编译器构建应用程序时,所有调度对象都是Objective-C对象。因此,当启用自动引用计数(ARC)时,将像任何其他Objective-C对象一样自动保留和释放分派对象。如果未启用ARC,请使用dispatch_retain和dispatch_release函数(或Objective-C语义)来保留和释放您的调度对象。您无法使用Core Foundation保留/释放功能。
如果您需要在启用ARC的应用程序中使用保留/释放语义以及更高版本的部署目标(以保持与现有代码的兼容性),则可以通过向您的-DOS_OBJECT_USE_OBJC = 0添加-DOS_OBJECT_USE_OBJC = 0来禁用基于Objective-C的调度对象编译器标志。
在您的情况下,ARC很乐意管理您dispatch_group_t
的生命周期。而且,不幸的是,您的代码导致在锁仍在等待时释放组。当组超时时它被释放 - 所以当调用dispatch_group_leave
时它会崩溃,因为该组已经被释放。
我建议至少在尝试离开之前检查组是否为NULL。
此外,您的等待结果逻辑是相反的。零结果表示组在超时之前被清空,非零结果表示超时被击中。