引言
在iOS中我们时常会提到一些僵尸对象,野指针,僵尸指针这样的名词;提到这些有些开发者就感到一种恐怖,一听“僵尸”是没有生命的,但是它确实是一种存在的类似生命体的一种生物。哈哈,当然本文的重点不是讨论“僵尸”,而是iOS当中经常遇到的僵尸对象(Zombie Object)。
野指针
先来介绍一下野指针,C/C++中对野指针的定义为:野指针就是指向垃圾内存的指针,这个指针地址不是NULL。如果给一个指针赋值为NULL,那么该指针就是一个空指针,这块内存被回收了或者被系统又分配给其他对象使用,只是指针不知道依然只想这块内存。
例如:
1)指针变量没有被初始化。任何指针在创建时都不会自动赋值为NULL,那么如果不初始化,它指向的内存地址是不确定的。所以在创建时,应该进行初始化。
char*ctr = NULL;
char *ctrs = (char*)malloc(32);
僵尸指针
“僵尸指针”就是野指针的一种情况,即该指针指向的对象已经被释放,但是却没有对当前指针赋值为nil。
僵尸对象
简单的来说,僵尸对象是已经被释放的对象。如果在程序中再度使用该对象,一般会出现如下报错:
解决方法
NSZombieEnabled变量用来调试与内存有关的问题,跟踪对象的释放过程。启用了NSZombieEnabled,它会用一个僵尸来替换默认的dealloc实现,也就是在引用计数降到0时,该僵尸实现会将该对象转换成僵尸对象。僵尸对象的作用是在你向它发送消息时,它会显示一段日志并自动跳入调试器。启用NSZombie而不是让应用直接崩溃掉时,一个错误的内存访问就会变成一条无法识别的消息发送给僵尸对象。僵尸对象会显示接受到得信息,然后跳入调试器,这样你就可以查看到底是哪里出了问题。
为什么不默认开启僵尸对象检测呢?
因为一旦开启,每次通过指针访问对象的时候,都会去检查指针指向的对象是否为僵尸对象。所以会影响程序的执行效率,建议关闭。
开启Zombie Object后,对象调用dealloc发生的变化
代码如下:
开启Zombie Objects
运行程序,查看打印信息。从打印信息可以看到开启僵尸对象检测后,People释放后所属类变成了_NSZombie_People。如此可得对象释放后会变成僵尸对象,保存当前释放对象的内存地址,防止被系统回收
结下来打开instruments ->Zombies ,查看dealloc 究竟做了什么。点击运行,查看Call trees。结果如下图,从dealloc的调用可以知道:Zombie Objects hook 住了对象的dealloc方法,通过调用自己的__dealloc_zombie方法来把对象进行僵尸化。在Runtime源码NSObject.mm文件中dealloc方法注释中也有说明这一点。如下:
由对象dealloc方法调用栈( Call Tree )很好的验证了步骤3的打印信息。我们来看看过程。
Zombie Object的生成过程是怎么样的
创建__dealloc_zombie符号断点来看一探究竟
从此处断点可以大概看出Zombie Object 的生成过程。_NSZombie_%s验证了开启僵尸对象检测后的对象所指向的类。从这个调用栈也可以说明系统开启僵尸对象检测后不会释放该对象所占用的内存,只是释放了与该对象所有的相关引用。让runtime源码告诉你:
上面是为开启僵尸对象检测对象释放的调用过程,开启僵尸对象检测后将没有free(obj)这一步的调用,而是执行objc_destructInstance(obj)方法后就直接return了。我们也可以看看objc_destructInstance到底都干了些什么。从其注释可以知道该方法做了下面几件事:【C++ destructors】【ARC ivar cleanup】【Removes associative references】并没有释放其内存。
从汇编的调用顺序总结出僵尸对象的生成过程:
Zombie Object是如何被触发的
1、再次调用[aPeople release]可以看到程序断在___forwarding___,从此处的汇编代码中可以看到关键字_NSZombie_,在调用abort( )函数退出进程时会有对应的信息输出@"*** -[%s %s]: message sent to deallocated instance %p"。所以可以大概猜出系统是在消息转发过程中做了手脚。
为此总结出它的调用过程:
总结
iOS在回收对象时,其实没有将其转化真的回收,而是把它转化为僵尸对象。
青山不改,绿水长流,后会有期,感谢每一位佳人的支持!