用“僵尸對象”調(diào)試內(nèi)存管理問題

?調(diào)試內(nèi)存管理問題很令人頭疼。大家都知道奸汇,向業(yè)已回收的對象發(fā)送消息是不安全的施符。這么做有時可以,有時不行擂找。具體可行與否戳吝,完全取決于對象所占內(nèi)存有沒有為其他內(nèi)容所復(fù)寫。而這塊內(nèi)存有沒有移作他用贯涎,又無法確定听哭,因此,應(yīng)用程序只是偶爾崩潰塘雳。在沒有崩潰的情況下陆盘,那塊內(nèi)存可能只復(fù)用了其中一部分,所以部分對象中的某些二進(jìn)制數(shù)據(jù)依然有效败明。還有一種可能隘马,就是那塊內(nèi)存恰好為另外一個有效且存貨的對象所占據(jù)。在這種情況下妻顶,運行期系統(tǒng)會把消息轉(zhuǎn)發(fā)到新對象那里酸员,而此對象也許能應(yīng)答,也許不能讳嘱。如果能幔嗦,那程序就不崩潰,可你會覺得奇怪:為什么收到消息的對象不是預(yù)想的那個呢沥潭?若新對象無法響應(yīng)選擇子邀泉,則程序依然會崩潰。

所幸Cocoa提供了“僵尸對象“(Zombie Object)這個非常方便的功能钝鸽。啟用這項調(diào)試功能之后呼渣,運行期系統(tǒng)會把所有已經(jīng)回收的實例轉(zhuǎn)化為特殊的”僵尸對象“,而不是真正回收他們寞埠。這種對象所在的核心內(nèi)存無法重用屁置,因此不可能遭到復(fù)寫。僵尸對象收到消息之后仁连,會拋出異常蓝角,其中準(zhǔn)確說明了發(fā)送過來的消息,并描述了回收之前的那個對象饭冬。僵尸對象是調(diào)試內(nèi)存管理問題的最佳方式使鹅。

將NSZombieEnabled環(huán)境變量設(shè)為YES,即可開啟此功能昌抠。給僵尸對象發(fā)消息后患朱,控制臺會打印消息,而應(yīng)用程序會終止炊苫。打印出來的消息就像這樣:

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

在xcode 中開啟僵尸對象調(diào)試如下圖所示:


so裁厅,關(guān)于僵尸對象的工作原理是什么呢冰沙?它的實現(xiàn)代碼深植于Objective -C的運行期程序庫、Foundation框架以及CoreFoundation框架中执虹。系統(tǒng)在即將回收對象時拓挥,如果發(fā)現(xiàn)通過環(huán)境變量啟用了僵尸對象功能,那么還將執(zhí)行一個附加步驟袋励。這一步就是把對象轉(zhuǎn)化為僵尸對象侥啤,而不徹底回收。

下列代碼有助于理解這一步所執(zhí)行的操作:


?為了便于演示普通對象轉(zhuǎn)化為僵尸對象的過程茬故,這段代碼采用了手動引用計數(shù)盖灸。因為假如使用ARC的話,str對象就會根據(jù)代碼需要磺芭,盡可能多存活一段時間赁炎,于是在這個簡單的例子中,就不可能變成僵尸對象了徘跪。這并不是說對象在ARC下絕不可能轉(zhuǎn)化為僵尸對象甘邀。即便用了ARC,也依然會出現(xiàn)這種內(nèi)存bug垮庐,只不過一般要通過稍微復(fù)雜些的代碼才能表現(xiàn)出來松邪。

范例代碼中有個函數(shù),可以根據(jù)給定的對象打印出所屬的類及其超類名稱哨查。此函數(shù)沒有直接給對象發(fā)送Objective-C的class消息逗抑,而是調(diào)用了運行期庫里的object_getClass()函數(shù)。因為如果參數(shù)已經(jīng)是僵尸對象了寒亥,那么給其發(fā)送Objective-C消息后邮府,控制臺會打印錯誤消息,而且應(yīng)用程序會崩潰溉奕。范例代碼將輸出下面這種消息:

Before release:

=== Zombie :NSObject ===

After release:

=== _NSZombie_Zombie :nil ===

對象所屬的類已由Zombie變?yōu)開NSZombie_Zombie褂傀。但是,這個新類是從哪里來的呢加勤?代碼中沒有定義過這樣一個類仙辟。而且,在啟用僵尸對象后鳄梅,如果編譯器每看到一種可能變成僵尸的對象叠国,就創(chuàng)建一個與之對應(yīng)的類,那也低效了戴尸。_NSZombie_Zombie實際上市在運行期生成的粟焊,當(dāng)首次碰到Zombie類的對象要變成僵尸對象時,就會創(chuàng)建那么一個類。在創(chuàng)建的過程中用到了運行期程序庫離得函數(shù)项棠,它們的功能很強(qiáng)大悲雳,可以操作類列表(class list)。

僵尸類(zombie class)是從名為_NSZombie_的模板類里復(fù)制出來的沾乘。這些僵尸類沒有多少事情可做怜奖,只是充當(dāng)一個標(biāo)記浑测。接下來介紹它們是怎么充當(dāng)標(biāo)記的翅阵。首先來看下面這段偽代碼,其中演示了系統(tǒng)如何根據(jù)需要創(chuàng)建出僵尸類迁央,而僵尸類有如何把待回收的對象轉(zhuǎn)化為僵尸對象掷匠。

這個過程其實就是NSObject的dealloc方法所做的事。運行期系統(tǒng)如果發(fā)現(xiàn)NSZombieEnabled環(huán)境變量已設(shè)置岖圈,那么就把”dealloc“方法的”調(diào)配“(swizzle)成一個會執(zhí)行上述代碼的版本讹语。執(zhí)行到程序末尾時,對象所屬的類已經(jīng)變?yōu)開NSZombie_OriginalClass了蜂科,其中OriginalClass指的是原類名顽决。

代碼中的關(guān)鍵之處在于:對象所占內(nèi)存沒有(通過調(diào)用free()方法)釋放,因此导匣,這塊內(nèi)存不可復(fù)用才菠。雖說內(nèi)存泄漏了,但這只是個調(diào)試手段贡定,發(fā)布正式應(yīng)用程序時不會把這項功能打開赋访,所以這種泄漏問題無關(guān)緊要。

但是缓待,系統(tǒng)為何要給每個變?yōu)榻┦念惗紕?chuàng)建一個對應(yīng)的模型呢蚓耽?這是因為,給僵尸對象發(fā)消息之后旋炒,系統(tǒng)可由此知道該對象原來所屬的類步悠。假如把所有僵尸對象都?xì)w到_NSZombie_類里,那原來的類名就丟了瘫镇。創(chuàng)建新類的工作由運行期函數(shù)objc_duplicateClass()來完成鼎兽,它會把整個_NSZombie_類結(jié)構(gòu)拷貝一份,并賦予其新的名字汇四。副本類的超類接奈、實例變量及方法都和復(fù)制前相同。還有種做法也能保留舊類名通孽,那就是不拷貝_NSZombie_序宦,而是創(chuàng)建繼承自_NSZombie_的新類,但是用響應(yīng)的函數(shù)完成此功能背苦,其效率不如直接拷貝高互捌。

僵尸類的作用會在消息轉(zhuǎn)發(fā)過程中體現(xiàn)出來潘明。_NSZombie_類(以及所有從該類拷貝出來的類)并未實現(xiàn)任何方法。此類沒有超類秕噪,因此和NSObject一樣钳降,也是個”根類“,該類只有一個實例變量腌巾,叫做isa遂填,所有Objective-C的根類都必須有此變量。由于這個輕量級的類沒有實現(xiàn)任何方法澈蝙,所以發(fā)給它的全部消息都要經(jīng)過”完整的消息轉(zhuǎn)發(fā)機(jī)制“吓坚。

在完整的消息轉(zhuǎn)發(fā)機(jī)制中,_ _ _forwarding_ _ _是核心,調(diào)試程序時灯荧,大家可能在椊富鳎回溯消息里看見過這個函數(shù)。它首先要做的事情就包括檢查接收消息的對象所屬的類名逗载。若名稱前綴為_NSZombie_哆窿,則表明消息接收者是僵尸對象,需要特殊處理厉斟。此時會打印一條消息挚躯,其中指明了僵尸對象所收到的消息及原來所屬的類,然后應(yīng)用程序就終止了捏膨。在僵尸類名中嵌入原始類名的好處秧均,這是就可以看出來了。只要把_NSZombie_從僵尸類中的開頭拿掉号涯,剩下的就是原始類名目胡。下列代碼演示了這一過程:


把開頭那個范例擴(kuò)充一下,試著給變成僵尸對象的Zombie對象發(fā)送description消息:

若是開啟了僵尸對象功能链快,那么控制臺會輸出如下消息:

Before release:

=== Zombie :NSObject ===

After release:

===_NSZombie_Zombie :nil ===

*** - [Zombie description]:message sent to deallocted instance 0x7fc821c02a00

大家可以看到誉己,這段消息明確指出了僵尸對象所收到的選擇子及其原來所屬的類,其中還包含接收消息的僵尸對象所對應(yīng)的”指針值“域蜗。在調(diào)試器中深入分析程序時巨双,也許會用到此消息,而且若能與適當(dāng)?shù)墓ぞ撸ū热鏧code自帶的Instruments)相搭配霉祸,則效果更佳筑累。

Above all:

~ 系統(tǒng)在回收對象時,可以不將其真的回收丝蹭,而是把它轉(zhuǎn)化為僵尸對象慢宗。通過環(huán)境變量NSZombieEnabled 可開啟此功能

~ 系統(tǒng)會修改對象的isa指針,令其指向特殊的僵尸類,從而使改對象變?yōu)榻┦瑢ο缶倒痢=┦惸軌蝽憫?yīng)所有的選擇子敏晤,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接收者的消息,然后終止應(yīng)用程序缅茉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嘴脾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蔬墩,更是在濱河造成了極大的恐慌译打,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筹我,死亡現(xiàn)場離奇詭異扶平,居然都是意外死亡帆离,警方通過查閱死者的電腦和手機(jī)蔬蕊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哥谷,“玉大人岸夯,你說我怎么就攤上這事∶峭祝” “怎么了猜扮?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長监婶。 經(jīng)常有香客問我旅赢,道長,這世上最難降的妖魔是什么惑惶? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任煮盼,我火速辦了婚禮,結(jié)果婚禮上带污,老公的妹妹穿的比我還像新娘僵控。我一直安慰自己,他們只是感情好鱼冀,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布报破。 她就那樣靜靜地躺著,像睡著了一般千绪。 火紅的嫁衣襯著肌膚如雪充易。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天荸型,我揣著相機(jī)與錄音盹靴,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛鹉究,可吹牛的內(nèi)容都是我干的宇立。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼自赔,長吁一口氣:“原來是場噩夢啊……” “哼妈嘹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绍妨,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤润脸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后他去,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毙驯,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年灾测,在試婚紗的時候發(fā)現(xiàn)自己被綠了爆价。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡媳搪,死狀恐怖铭段,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秦爆,我是刑警寧澤序愚,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站等限,受9級特大地震影響爸吮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜望门,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一形娇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧怒允,春花似錦埂软、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至丽惶,卻和暖如春炫七,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钾唬。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工万哪, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留侠驯,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓奕巍,卻偏偏與公主長得像吟策,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子的止,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容