作用
簡單來說就用來Debug野指針的情況夸盟。
我們向一個被釋放的對象發(fā)送消息是不安全的蛾方,但是有時候又沒有問題。這主要取決于這個對象之前所在的內存空間是否被重寫過了,這是不確定的桩砰,因此所造成的情況也是不確定的拓春。當這塊內存空間被復寫為另一個對象的時候,如果不能識別我們發(fā)送的消息亚隅,那么崩潰是必然的硼莽。如果有時候能被識別,那么debug起來就非常困難煮纵。
Zombies
Cocoa 有個很好的功能:“Zombies”懂鸵,此時就能派上很大用處。當“Zombies”開啟的時候行疏,runtime將會把所有要被銷毀(deallocated)的對象變成特殊的僵尸對象匆光,而不是按正常流程進行銷毀。而存放這些僵尸對象的內存空間是不可復用的酿联,從而杜絕以上這種野指針的情況终息。
此時我們向一個zombie發(fā)送消息的話,將會拋出異常并明確告知消息被發(fā)送到了一個已經銷毀的對象上贞让。
如下:
*** -[CFString respondToSelector:]: message sent to deallocated instance 0x7ff9e9c080e0
使用
在Xcode中:
Edit Scheme
按照圖中注解給Zombie Objects打上勾即可周崭。
原理
主要還是依賴于runtime、Foundation和CoreFoundation框架喳张。如果我們打開了Zombie Objects選項休傍,當一個對象即將deallocated的時候,將會額外多一步蹲姐,就是額外的這一步將該對象轉換為僵尸對象而不是直接deallocated磨取。
下面一段代碼做參考:
@interface EOCClass : NSObject
@end
@implementation EOCClass
@end
void PrintClassInfo(id obj) {
Class cls = object_getClass(obj);
Class superCls = class_getSuperclass(cls);
NSLog(@"===%s : %s ===",class_getName(cls),class_getName(superCls));
}
int main(int argc, char * argv[]) {
@autoreleasepool {
EOCClass *obj = [[EOCClass alloc] init];
NSLog(@"Before release:");
PrintClassInfo(obj);
[obj release];
NSLog(@"After release:");
PrintClassInfo(obj);
}
}
這段代碼用了MRC,以此更方便展示將是對象的產生赂摆。
最后的運行結果如下:
Before release:
=== EOCClass : NSObject ===
After release:
=== _NSZombie_EOCClass : nil ===
從上可以看出执解,當一個對象變成僵尸的時候憋槐,它的類也從EOCClass變成了_NSZombie_EOCClass悦穿。問題是支救,這個類是哪里來的晒哄?
自然而然我們會想到runtime創(chuàng)建了這個僵尸類毁习。
以上這個僵尸類是模板NSZombie類的一個副本牍鞠,它并沒有什么別的作用歼指,只是簡單的作為一個標記爹土。
以下是一段偽代碼,大致展現了這個僵尸類是如何創(chuàng)建的踩身,并且該對象是如何變成一個僵尸對象的:
//Obtain the class of the object being deallocated 獲得將要釋放的對象的類
Class cls = object_getClass(self);
//Get the class's name 獲得類名
const char *clsName = class_getName(cls);
//Prepend _NSZombie_ to the class name 提前擴展好需要的類名
const char *zombieClsName = "_NSZombie_" + clsName;
// See if the specific zombie class exists 檢查該類是否存在
Class zombieCls = objc_lookUpClass(zombieClsName);
//If the specific zombie class doesn't exist,
//then it needs to be created 如果不存在胀茵,則創(chuàng)建
if (!zombieCls) {
//Obtain the template zombie class called _NSZombie_ 獲得模板類_NSZombie_
Class baseZombieCls = objc_loopUpClass("_NSZombie_");
//Duplicate the base zombie class,where the new class's name is the prepended string from above 以模板為基礎重建一個類,類名為以上的zombieClsName字符串
zombieCls = objc_duplicateClass(baseZombieCls,zombieClsName,0);
}
//Perform normal destruction of the object being deallocated 執(zhí)行一般的銷毀流程
objc_destructInstance(self);
//Set the class of the object being deallocated to the zombie class 講對象的類設置為僵尸類
objc_setClass(self,zombieCls);
//the class of 'self' is now _NSZombie_OriginalClass
當NSZombieEnabled 這個選項開啟的時候挟阻,runtime會將上述代碼與之前常規(guī)的dealloc代碼進行互換琼娘,由此來保證對象的類變成僵尸類峭弟。
關鍵一點是,這個內存中的對象其實還是活著的脱拼,內存并沒有被釋放瞒瘸,因此該內存也不會被重復使用。因為對象被標記為了僵尸熄浓,所以接收到消息的時候能提示我們異常所在情臭。
之所以大費周章的給每一個對象的類都重新制定一個相對應的僵尸類是因為這樣在反饋問題的時候會顯得更加精準一些,如果都簡單的報錯NSZombie對象無法識別方法赌蔑,那么debug效果就幾乎沒有了
NSZombie
NSZombie本身并不實現任何方法俯在,也沒有父類,所以它是一個基類惯雳,就像NSObject一樣朝巫。因為它不實現任何方法,所以當接收到消息的時候石景,會完整的走一遍消息轉發(fā)流程劈猿。
消息轉發(fā)中關鍵的一環(huán)是forwarding,它做的其中一件事情就是先檢查對象的類名是否含有前綴NSZombie,如果檢測到了,那么就直接走報告僵尸對象的流程潮孽。再打印完錯誤信息之后程序就結束運行了揪荣。
以下這段偽代碼可以幫助理解在forwarding里是怎么處理zombie對象的:
//Obtain the object's class 取得對象的類
Class cls = object_getClass(self);
//Get the class's name 取得類名
const char *clsName = class_getName(cls);
//Check if the class is prefixed with _NSZombie_ 檢查是否含有前綴_NSZombie_
if(string_has_prefix(clsName,"_NSZombie_")) {
//if so, this object is a zombie 如果前綴符合,那么它是一個僵尸對象
//Get the original class name by skipping past the _NSZombie_, i.e. taking the substring from character 10 獲取原始的類名
const char *originalClsName = substring_from(clsName,10);
//Get the selector name of the message 獲取方法名
const char *selectorName = sel_getName(_cmd);
//Log a message to indicate which selector is being sent yo which zombie 打印錯誤信息
Log("*** -[%s %s]: message sent to deallocated instance %p", originalClsName,selectorName,self);
//Kill the application 結束程序
abort();
}