iOS EXC_BAD_INSTRUCTION类型 奔溃

2018-07-12  本文已影响105人  路飞_Luck
序言

在做项目的时候,遇到一个巨坑,在使用dispatch_group 的时候遇到奔溃,此问题较难定位,但是解决方法较为简单,详细如下所示。

crash 场景
1. 有一组多个接口地址
2. 利用dispatch_group 并发请求到数据后,统一回调(必须利用 AFN进行网络请求)
3. 频繁调用(必要)
4. 每次调用2中的请求是同一个接口地址(必要)
备注:频繁的意思是一秒调用3次或3次以上

问题核心:

对dispatch_group 进行了额外的 leave 操作

问题代码

- (void)errorRequest {
    dispatch_group_t group = dispatch_group_create();
    self.group = group;
    
    // enter code
    [request  requestGetUrl:url success:^(id responds) {
        // leave code
    }];
}

修正后代码

- (void)rightRequest {
    if (self.group == nil) {
        dispatch_group_t group = dispatch_group_create();
        self.group = group;
    }
    
    // enter code
    [request  requestGetUrl:url success:^(id responds) {
        // leave code
    }];
}

产生此问题的原因:概况的说是dispatch_group 的原理和 AFNetworking 网络请求回调 block 的缓存回调原理的协作问题。

举例详细说明,流程图如下:

image.png
1. 正常情况下:执行步骤1的问题间隔时间充分长,或者只执行一次,此逻辑没有问题,不会 crash。
2. 非正常情况下:开头所说的 crash 出现场景下,即频繁执行上图中步骤1至步骤3.

因为网络请求的耗时和异步特性,有时候会发现一些情况

1. 第一次在步骤1创建了一个新的 group1,这时网络请求 n1到 n4请求未返回,也就是b1到 b4还未返回。
2. 此时又执行了一次步骤一创建了一个新的group,命名为 group2(这是一个新的),并且赋值给了self.group,又执行步骤二,对 group2进行 enter,发送网络请求。
3. 此时若是1中的网络请求返回了,b1到 b4就会调用,会对步骤2中创建的新的 group2进行 leave 操作,当2中的网络请求返回,进行回调时,会发生对 group2进行额外的 leave 操作,从而造成 crash。
注:b1到 b4因为 leave 的需要,会对 group 进行地址引用。

模拟网络请求奔溃代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_group_t group = dispatch_group_create();
    self.group = group;
    
    dispatch_group_enter(self.group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        dispatch_group_leave(self.group);
    });
    
    dispatch_group_enter(self.group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        dispatch_group_leave(self.group);
    });

    dispatch_group_enter(self.group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        dispatch_group_leave(self.group);
    });

    dispatch_group_notify(self.group, dispatch_get_main_queue(), ^{
        NSLog(@"操作全部完成");
    });
}

多次点击屏幕后发生crash

image.png
修正后网络请求代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if (self.group == nil) {
        dispatch_group_t group = dispatch_group_create();
        self.group = group;
    }
    
    dispatch_group_enter(self.group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        dispatch_group_leave(self.group);
    });
    
    dispatch_group_enter(self.group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        dispatch_group_leave(self.group);
    });

    dispatch_group_enter(self.group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        dispatch_group_leave(self.group);
    });

    dispatch_group_notify(self.group, dispatch_get_main_queue(), ^{
        NSLog(@"操作全部完成");
    });
}

运行结果

image.png

没有发生奔溃


本文参考iOS开发--Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) 的一类解决办法,非常感谢该作者。

iOS疑难问题排查之深入探究dispatch_group crash,这篇博客介绍的非常详细,有深度。


项目连接地址

上一篇下一篇

猜你喜欢

热点阅读