高效編寫代碼的方法(二十六):使用“Zombies”來Debug

作用

簡單來說就用來Debug野指針的情況夸盟。
我們向一個被釋放的對象發(fā)送消息是不安全的蛾方,但是有時候又沒有問題。這主要取決于這個對象之前所在的內存空間是否被重寫過了,這是不確定的桩砰,因此所造成的情況也是不確定的拓春。當這塊內存空間被復寫為另一個對象的時候,如果不能識別我們發(fā)送的消息亚隅,那么崩潰是必然的硼莽。如果有時候能被識別,那么debug起來就非常困難煮纵。

Zombies

Cocoa 有個很好的功能:“Zombies”懂鸵,此時就能派上很大用處。當“Zombies”開啟的時候行疏,runtime將會把所有要被銷毀(deallocated)的對象變成特殊的僵尸對象匆光,而不是按正常流程進行銷毀。而存放這些僵尸對象的內存空間是不可復用的酿联,從而杜絕以上這種野指針的情況终息。
此時我們向一個zombie發(fā)送消息的話,將會拋出異常并明確告知消息被發(fā)送到了一個已經銷毀的對象上贞让。
如下:

*** -[CFString respondToSelector:]: message sent to  deallocated instance 0x7ff9e9c080e0

使用

在Xcode中:
Edit Scheme

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();
}  
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末往史,一起剝皮案震驚了整個濱河市仗颈,隨后出現的幾起案子,更是在濱河造成了極大的恐慌椎例,老刑警劉巖挨决,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異订歪,居然都是意外死亡脖祈,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門刷晋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盖高,“玉大人,你說我怎么就攤上這事眼虱∮靼拢” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵捏悬,是天一觀的道長撞蚕。 經常有香客問我,道長邮破,這世上最難降的妖魔是什么诈豌? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任仆救,我火速辦了婚禮抒和,結果婚禮上矫渔,老公的妹妹穿的比我還像新娘。我一直安慰自己摧莽,他們只是感情好庙洼,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著镊辕,像睡著了一般油够。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上征懈,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天石咬,我揣著相機與錄音,去河邊找鬼卖哎。 笑死鬼悠,一個胖子當著我的面吹牛,可吹牛的內容都是我干的亏娜。 我是一名探鬼主播焕窝,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼维贺!你這毒婦竟也來了它掂?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤溯泣,失蹤者是張志新(化名)和其女友劉穎虐秋,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體垃沦,經...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡客给,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了栏尚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片起愈。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖译仗,靈堂內的尸體忽然破棺而出抬虽,到底是詐尸還是另有隱情,我是刑警寧澤纵菌,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布阐污,位于F島的核電站,受9級特大地震影響咱圆,放射性物質發(fā)生泄漏笛辟。R本人自食惡果不足惜功氨,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望手幢。 院中可真熱鬧捷凄,春花似錦、人聲如沸围来。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽监透。三九已至桶错,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胀蛮,已是汗流浹背院刁。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留粪狼,地道東北人退腥。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像鸳玩,于是被迫代替她去往敵國和親阅虫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容