啥是野指针?
指向一个已经删除的对象或未申请访问受限内存区域的指针。而这里的野指针主要是指对象释放后,指针未置空导致的野指针。该类Crash发生比较随机,找出来比较费劲,比较常见的做法是在开发阶段就提高这类Crash的复现率,尽可能的将其发现并解决。
向OC对象发出release消息,只是标记对象占用的那块内存可以被释放,系统并没有立即收回内存;如果此时还向该对象发送其他消息,可能会发生Crash,也可能没有问题。
野指针造成的Crash随机性比较大,但是被随机填入的数据是不可访问的情况下,Crash是必现的。
解决思路是:想办法给野指针指向的内存填写不可访问的数据,让随机的Crash变成必现的Crash。
如何定位野指针
1.内存涂鸦(Malloc Scribble)
Xcode提供的Malloc Scribble,可以将对象释放后在内存上填上不可访问的数据,将随机发生变成不随机发生的事情,选中Product->Scheme->Edit Scheme ->Diagnostics – >勾选 Malloc Scribble项,结果如下:
设置了Enable Scribble,在对象申请内存后在申请的内存上填0xaa,内存释放后在释放的内存上填0x55;如果内存未被初始化就被访问,或者释放后被访问,Crash必现。
Warning:该方法必须连接Xcode运行代码才能发现,并不适合测试人员使用。
- 僵尸对象(NSZombieEnabled)
- 内存已经被回收的对象。
- 简单的来说,僵尸对象是已经被释放的对象。如果在程序中再度使用该对象,一般会出现如下报错:
unrecognized selector sent to instance .
默认情况下. Xcode不会去检测指针指向的对象是否为1个僵尸对象. 能访问就访问 不能访问就报错.
// 首先判断对象a是否还存在,如存在,执行mehtod方法;若不存在,此时就是对象a就是僵尸对象,此时如果不判断直接调用method方法,就会crash
if(!a){
a = [[A alloc] init];
}
[a method];
Xcode提供的NSZombieEnabled,通过生成僵尸对象来替换dealloc的实现,当对象引用计数为0 的时候,将需要dealloc的对象转化为僵尸对象。如果之后再给这个僵尸对象发消息则抛异常。先选中Product -> Scheme -> Edit Scheme -> Diagnostics -> 勾选Zombie Objects 项,显示如下:
然后在Product -> Scheme -> Edit Scheme -> Arguments设置NSZombieEnabled、MallocStackLoggingNoCompact两个变量,且值均为YES。显示如下:
- 仅设置Zombie Objects的话,如果Crash发生在当前调用栈,系统可以把崩溃原因定位到具体代码中;但是如果Crash不是发生在当前调用栈,系统仅仅告知崩溃地址,所以需要添加变量。MallocStackLoggingNoCompact,让Xcode记录每个地址alloc的历史,然后通过命令将地址还原出来。
注意:发版前要将僵尸对象检测这些设置都去掉,否则每次通过指针访问对象时,都去检查指针指向的对象是否为僵尸对象,这就影响效率了。
为什么不默认开启僵尸对象检测呢?
因为一旦开启,每次通过指针访问对象的时候.都会去检查指针指向的对象是否为僵尸对象.
那么这样的话 就影响效率了.
- 如何避免僵尸对象报错.
- 当1个指针变为野指针以后. 就把这个指针的值设置为nil
- 僵尸对象无法复活.
- 当1个对象的引用计数器变为0以后 这个对象就被释放了。
- 就无法取操作这个僵尸对象了,所有对这个对象的操作都是无效的。
因为一旦对象被回收,对象就是1个僵尸对象,而访问1个僵尸对象是没有意义。
如何定位Obj-C野指针随机Crash(一):先提高野指针Crash率
如何定位Obj-C野指针随机Crash(二):让非必现Crash变成必现
如何定位Obj-C野指针随机Crash(三):加点黑科技让Crash自报家门