​ 我司的测试妹子真的非常的敬业和执着,在一轮发测过程中出现了一次闪退,并且把crash文件传给研发排查。研发排查了一会发现没有什么特别的直观错误,就把问题搁置了,但是测试一直执着要研发给出结论,才有了下面的故事。

​ 首先该问题在持续5天的测试中只出现了一次,第二crash信息里面没有非常直观的代码异常,第三研发同事排查了半天没有结果领导要求我去帮忙。

​ 拿到crash文件并且通过符号表转化后,的确很懵逼。

Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Codes: 0x00000000 at 0x0000000a0b16e290
Crashed Thread:  36


Thread 36 Crashed:
0   libobjc.A.dylib                 0x00000001828e3b30 0x1828e2000 + 6960 (objc_msgSend + 16)
1   libdispatch.dylib               0x000000018288a9a8 0x182830000 + 371112 (<redacted> + 24)
2   libdispatch.dylib               0x000000018288b524 0x182830000 + 374052 (<redacted> + 16)
3   libdispatch.dylib               0x0000000182868b3c 0x182830000 + 232252 (<redacted> + 564)
4   libdispatch.dylib               0x000000018286954c 0x182830000 + 234828 (<redacted> + 396)
5   libdispatch.dylib               0x000000018287284c 0x182830000 + 272460 (<redacted> + 580)
6   libsystem_pthread.dylib         0x00000001828dcb74 0x1828d1000 + 47988 (_pthread_wqthread + 272)

野指针异常,在一个异步线程中执行某个对象的函数,但是该函数已经被释放了。如果在Debug模式下,并且有复现的步骤,我们可以通过僵尸对象的方式排查定位是哪个对象导致的。但是现在的情况,我只能通过分析其他的堆栈信息找找有没有线索。好在某个线程中找到了一点线索,大概是这样的。

- (void)doo{
    [self a];
    [self.delegate finish];//委托的实现把当前对象=nil
    [self b]; // EXC_BAD_ACCESS
}

我就大概写了demo ,当你点击create,程序闪退了。

//测试的OC对象
@implementation TestOBJ2

- (void)dealloc{
    NSLog(@"TestOBJ2 dealloc");
}

- (void)start{
    [self print:@"1"];
    [self.delegate finish];
    [self print:@"2"];
}

- (void)print:(NSString *)message{
    NSLog(message);
}

@end
//创建OC对象,并且赋值,并且在回调里面把对象设置为nil
@property(strong,nonatomic) TestOBJ2* testob2;

- (IBAction)create:(id)sender {
    self.testob2 = [[TestOBJ2 alloc] init];
    self.testob2.delegate = self;
    
    [_testob2 start];
}


- (void)finish{
    self.testob2 = nil;
}

截屏2020-11-26 下午10.58.29.png

入参的self在函数没有执行完毕的时候就被释放了,打开僵尸对象再来一次。可以非常肯定,的确被释放了。

020-11-26 23:07:28.138426+0800 EXC_BAD_ACCESS_Crash[5809:246951] *** -[TestOBJ2 print:]: message sent to deallocated instance 0x600003b84880

这看起来的确有点不和常理啊,我就delegate 回调里面设置为nil,我当前应该还强引用这对象啊,怎么会释放呢?平时不都这么写代码的么。

thread_17446321_20191023180744_s_4436_w_240_h_240_25739.jpeg

好像问题出在这个self中了,在ARCself到底是个什么。

The self parameter variable of an non-init Objective-C method is considered externally-retained by the implementation. It is undefined behavior, or at least dangerous, to cause an object to be deallocated during a message send to that object. In an init method, self follows the :ref:init family rules <arc.family.semantics.init>.

大概的意思,除了init 等函数,在其他函数作用域里面 self__unsafe_unretained;那怎么保障对象呢,谁创建谁管理。仔细想想好像的确那么一回事,如果你把self当成一个函数的第一参数,那么这个参数的生命周期不应该由函数来保障的。

后话

我们再修改下代码:

- (IBAction)create:(id)sender {
    self.testob2 = [[TestOBJ2 alloc] init];
    self.testob2.delegate = self;
    
    [self.testob2 start];//正常
    [_testOb2 start];//闪退
}

为什么,用self.testob2 就又正常了呢?AutoReleasePool的延迟释放。但是异步线程的闪退,还是无法避免。