崩溃描述
image.png近日排查线上崩溃时,发现一个描述信息很少的崩溃,如上。由dispatch_group_leave.cold.1可知,属于dispatch_group异常
dispatch_group使用
dispatch_group使用场景:A任务依赖B/C/D子任务全部执行完成,才进行触发执行。
如何添加子任务,通常有两种方式:
- block
- 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)exortImage:(NSArray<NSString *> *)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;
}
复现步骤:
假如infos只有一条数据,单个导出任务耗时10s,任务A在进行到5s时,取消任务A,重新触发新的导出B,A/B均完成时,就会崩溃。
原因分析:
- NSOperationQueue已经调度的任务,其实是无法直接取消的,任务仍会继续执行
- A任务再经历5s后完成,调用
dispatch_group_leave(weak_self.group);
,但其实A创建的group已释放,调用的是B的group.leave,计数为LONG_MAX,并触发notify。 - 当B也完成任务时,再次调用dispatch_group_leave,导致崩溃。
网友评论