dispatch_group_leave崩溃

崩溃描述

image.png

近日排查线上崩溃时,发现一个描述信息很少的崩溃,如上。由dispatch_group_leave.cold.1可知,属于dispatch_group异常

dispatch_group使用

dispatch_group使用场景:A任务依赖B/C/D子任务全部执行完成,才进行触发执行。
如何添加子任务,通常有两种方式:

  1. block
  2. dispatch_group_enter+dispatch_group_leave
    dispatch_group_t group = dispatch_group_create();

    // 添加N个子任务...
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 耗时任务task1
        dispatch_group_leave(self.group);
    });
    // ...
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"task123完成,收到通知");
    });

为什么dispatch_group_leave崩溃?

dispatch_group维持有一个计数。

  • dispatch_group_create初始化为LONG_MAX
  • dispatch_group_leave时+1
  • dispatch_group_enter时-1。

当leave次数超出enter次数时,LONG_MAX+1溢出,触发Crash。

陷阱规避

1. 属性记录dispatch_group_t,易引发错位leave

示例错误代码:

// 强引用group对象
@property (nonatomic, strong) dispatch_group_t group;
@property (nonatomic, strong) NSOperationQueue *queue;

- (void)cancel {
    self.cancelled = YES;
    [self.queue cancelAllOperations];
}

- (void)exportImage:(NSArray *)infos {
    self.group = dispatch_group_create();
    self.cancelled = NO;
    
    __weak typeof(self) weak_self = self;
    for (NSString *info in infos) {
        dispatch_group_enter(self.group);
        [self.queue addOperationWithBlock:^{
            // 大量计算与io耗时操作
            // ...
            dispatch_group_leave(weak_self.group);
        }];
    }
    dispatch_group_notify(self.group, dispatch_get_main_queue(), ^{
        if (!weak_self.cancelled) {
            // 导出完成,下一步
        }
    });
}

- (NSOperationQueue *)queue {
    if (!_queue) {
        _queue = [[NSOperationQueue alloc] init];
        _queue.maxConcurrentOperationCount = 2;
    }
    return _queue;
}

复现步骤:

Demo,点击开始,4s内点击结束再点击开始,等待一会,崩溃。

假如infos只有一条数据,单个导出任务耗时10s,任务A在进行到5s时,取消任务A,重新触发新的导出B,A/B均完成时,就会崩溃。

原因分析:

  1. NSOperationQueue已经调度的任务,其实是无法直接取消的,任务仍会继续执行
  2. A任务再经历5s后完成,调用dispatch_group_leave(weak_self.group);,但其实A创建的group已释放,调用的是B的group.leave,计数为LONG_MAX,并触发notify。
  3. 当B也完成任务时,再次调用dispatch_group_leave,导致崩溃。

2. 如何规避?

dispatch_group_t改用局部变量即可。
示例正确代码:

// 强引用group对象
@property (nonatomic, strong) NSOperationQueue *queue;

- (void)cancel {
    self.cancelled = YES;
    [self.queue cancelAllOperations];
}

- (void)exportImage:(NSArray *)infos {
    dispatch_group_t group = dispatch_group_create();
    self.cancelled = NO;
    
    __weak typeof(self) weak_self = self;
    for (NSString *info in infos) {
        dispatch_group_enter(group);
        [self.queue addOperationWithBlock:^{
            // 大量计算与io耗时操作
            // ...
            dispatch_group_leave(group);
        }];
    }
    dispatch_group_notify(.group, dispatch_get_main_queue(), ^{
        if (!weak_self.cancelled) {
            // 导出完成,下一步
        }
    });
}

- (NSOperationQueue *)queue {
    if (!_queue) {
        _queue = [[NSOperationQueue alloc] init];
        _queue.maxConcurrentOperationCount = 2;
    }
    return _queue;
}

你可能感兴趣的:(dispatch_group_leave崩溃)