ARC下self真的安全么
我司的测试妹子真的非常的敬业和执着,在一轮发测过程中出现了一次闪退,并且把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;
}
入参的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,我当前应该还强引用这对象啊,怎么会释放呢?平时不都这么写代码的么。
好像问题出在这个self中了,在ARC下self到底是个什么。
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的延迟释放。但是异步线程的闪退,还是无法避免。