《編寫高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》--第五章 第35條
(ps:此乃讀書筆記,加深記憶,僅供大家參考)
第35條 用“僵尸對(duì)象”調(diào)試內(nèi)存管理問(wèn)題
大家都知道,向業(yè)已回收的對(duì)象發(fā)送消息是不安全的。這么做有時(shí)可以,有時(shí)不行。具體可行與否,完全取決于對(duì)象所占內(nèi)存有沒(méi)有為其他內(nèi)容所覆寫。
所幸Cocoa提供了“僵尸對(duì)象”(Zoombie Object)這個(gè)非常方便的功能。啟用這項(xiàng)調(diào)試功能之后,運(yùn)行期系統(tǒng)會(huì)把所有已回收的實(shí)例轉(zhuǎn)化成特殊的“僵尸對(duì)象”,而不會(huì)真正回收它們。這種對(duì)象所在的核心內(nèi)存無(wú)法重用,因此不可能遭到覆寫。僵尸對(duì)象收到消息后,會(huì)拋出異常,其中準(zhǔn)確說(shuō)明了發(fā)送過(guò)來(lái)的消息,并描述了回收之前的那個(gè)對(duì)象。僵尸對(duì)象是調(diào)試內(nèi)存管理問(wèn)題的最佳方式。
僵尸對(duì)象的工作原理是什么?它的實(shí)現(xiàn)代碼深植于Objective-C的運(yùn)行期程序庫(kù)、Foundation框架及CoreFoundation框架中。系統(tǒng)在即將回收對(duì)象時(shí),如果發(fā)現(xiàn)通過(guò)環(huán)境變量啟用了僵尸對(duì)象功能,那么還將執(zhí)行一個(gè)附加步驟。這一步驟就是把對(duì)象轉(zhuǎn)化為僵尸對(duì)象,而不徹底回收。
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[])
{
EOCClass *obj = [[EOCClass alloc] init];
NSLog(@"Before release:");
PrintClassInfo(obj);
[obj release];
NSLog(@"After release:");
PrintClassInfo(obj);
}
范例代碼將輸出下面這種消息:
Before release:
=== EOCClass : NSObject ===
After release:
=== _NSZombie_EOCClaxx : nil ===
_NSZombie_EOCClaxx實(shí)際上是在運(yùn)行期生成的,當(dāng)首次碰到EOCClass類的對(duì)象要變成僵尸對(duì)象時(shí),就會(huì)創(chuàng)建這么一個(gè)類。創(chuàng)建過(guò)程中用到了運(yùn)行期程序庫(kù)里的函數(shù),他們的功能很強(qiáng)大,可以操作類列表(class list)。
僵尸類(zoombie class)是從名為NSZombie的模板類里復(fù)制出來(lái)的。這些僵尸類沒(méi)有多少事情可做,只是充當(dāng)一個(gè)標(biāo)記。
僵尸類的作用會(huì)在消息轉(zhuǎn)發(fā)例程(參見12條)中體現(xiàn)出來(lái)。NSZombie類(以及所有從該類拷貝出來(lái)的類)并未實(shí)現(xiàn)任何方法。此類沒(méi)有超類,因此和NSObject一樣,也是個(gè)“根類”,該類只有一個(gè)實(shí)例變量,叫做isa,所有NSObjective-C的根類都必須由此變量。由于這個(gè)輕量級(jí)的類沒(méi)有實(shí)現(xiàn)任何方法,所以發(fā)給它的全部消息都要經(jīng)過(guò)“完整的消息轉(zhuǎn)發(fā)機(jī)制”(full forwarding mechanism, 參見第12條)。
在完整的消息轉(zhuǎn)發(fā)機(jī)制中,forwarding是核心,調(diào)試程序時(shí),大家可能在棧回溯消息里看見過(guò)這個(gè)函數(shù)。它首先要做的事情就包括檢查接收消息的對(duì)象所屬的類名。若名稱前綴為NSZombie,則表明消息接收者是僵尸對(duì)象,需要特殊處理。此時(shí)會(huì)打印一條消息,其中指明了僵尸對(duì)象所受到的消息及原來(lái)所屬的類,然后應(yīng)用程序就終止了。
* * * -[CFString respondsToSelector:]: message sent to deallocated instance 0x7ff9e9c080e0
把本節(jié)開頭那個(gè)范例擴(kuò)充一下,試著給變成僵尸的EOCClass對(duì)象發(fā)送description消息:
EOCClass *obj = [[EOCClass alloc] init];
NSLog(@"Before release:");
PrintClassInfo(obj);
[obj release];
NSLog(@"After release:");
PrintClassInfo(obj);
[obj description];
若是開啟了僵尸對(duì)象功能,那么控制條會(huì)輸出下列消息:
Before release:
=== EOCClass : NSObject ===
After release:
=== _NSZombie_EOCClass : nil ===
*** -[EOCClass description]: message sent to deallocated instance 0x7fb81bdce9b0
要點(diǎn)
- 系統(tǒng)在回收對(duì)象時(shí),可以不將其真的回收,而是把它轉(zhuǎn)化為僵尸對(duì)象。通過(guò)環(huán)境變量NSZombieEnable可開啟此功能
- 系統(tǒng)會(huì)修改對(duì)象的isa指針,令其指向特殊的僵尸類,從而使該對(duì)象變?yōu)榻┦瑢?duì)象。僵尸類能夠響應(yīng)所有的選擇子,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接受者的消息,然后終止應(yīng)用程序。