?調(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)用程序缅茉。