使用dispatch_block

时间:2015-10-01 08:49:07

标签: ios objective-c block objective-c-blocks grand-central-dispatch

我一直试图了解这次崩溃背后的原因,以便更好地了解块的行为方式。我有一个非常简单的类来触发这次崩溃。

@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_。   我已经能够使用我的代码重现完全相同的堆栈跟踪   上方。

3 个答案:

答案 0 :(得分:3)

有几点意见:

  1. 取消分配dispatch_group_t对象时,您不能拥有不平衡的“enter”和“leave”的调度组。正如ilya指出的那样,由于你的模式,strongSelfnil,所以你进入群组,但不会离开。

    weakSelfstrongSelf舞蹈中非常常见的模式是检查strongSelf是否nil,以解决不平衡问题。因此,如果strongSelfnil,则会完全绕过调度组内容,但如果不是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方法本身总是调用离开该组的块。

  2. 解决这个问题的另一种方法(如果你没有保证所有的“进入”和“离开”将是平衡的),就是使用信号量:

    - (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的程序集跟踪: enter image description here

如果我们试图弄清楚此代码中发生了什么,我们会看到您明确标记当前阻止通过调用dispatch_group_enter进入群组。然后performSomethingAsync未调用,因为strongSelf == nil。这意味着dispatch_group_enterdispatch_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。

此外,您的等待结果逻辑是相反的。零结果表示组在超时之前被清空,非零结果表示超时被击中。