野指針:指針指向的對(duì)象已經(jīng)被回收掉了。這個(gè)指針就叫做野指針渊季。
僵尸對(duì)象:一個(gè)已經(jīng)被釋放的對(duì)象 就叫做僵尸對(duì)象
OC中是怎么實(shí)現(xiàn)的呢
首先如何打開Zombile Object 調(diào)試模式呢?
這個(gè)問題相信大家都不陌生:
第一步罚渐、打開Xcode 選擇屏幕左上角Xcode-> PReferencese
設(shè)置一下輸出信息却汉,調(diào)試的時(shí)候能夠輸出更多的輸出信息
第二步再對(duì)環(huán)境變量進(jìn)行設(shè)置菜單 Product->Scheme->EditScheme
勾選上下列選項(xiàng):
這樣程序運(yùn)行時(shí)如果訪問了已經(jīng)釋放的對(duì)象,則會(huì)精準(zhǔn)的的定位信息荷并,拋出異常 合砂。該功能的原理是在對(duì)象釋放時(shí),使用一個(gè)內(nèi)置的 Zombie,替代原來的被釋放的對(duì)象源织。
sudo malloc_history 進(jìn)程 ID 內(nèi)存地址翩伪,可以查看錯(cuò)誤日志。
現(xiàn)在我們知道什么是僵尸對(duì)象(Zombie Object)了嗎谈息?其實(shí)他就是一種用來檢測(cè)內(nèi)存錯(cuò)誤(EXC_BAD_ACCESS)的對(duì)象缘屹。
那當(dāng)我勾選了這些對(duì)象時(shí),系統(tǒng)其實(shí)做了哪些操作呢侠仇?
為什么勾選了這些轻姿,就能有這些效果呢犁珠?
iOS Zombie Objects(僵尸對(duì)象)原理探索
1. Zombie Object 有什么用
僵尸對(duì)象一種用來檢測(cè)內(nèi)存錯(cuò)誤(
EXC_BAD_ACCESS
)的對(duì)象,它可以捕獲任何對(duì)嘗試訪問壞內(nèi)存的調(diào)用互亮。如果給僵尸對(duì)象發(fā)送消息時(shí)犁享,那么將在運(yùn)行期間崩潰和輸出錯(cuò)誤日志。通過日志可以定位到野指針對(duì)象調(diào)用的方法和類名豹休。
2. 如何開啟Zombie Object檢測(cè)
在Xcode中設(shè)置Edit Scheme -> Diagnostics -> Zombie Objects
3. 開啟Zombie Object檢測(cè)后炊昆,對(duì)象調(diào)用dealloc方法會(huì)發(fā)生變化
1、新建一個(gè)終端工程(Command Line Tool)威根,具體代碼如下:
void printClassInfo(id obj)
{
Class cls = object_getClass(obj);
Class superCls = class_getSuperclass(cls);
NSLog(@"self:%s - superClass:%s", class_getName(cls), class_getName(superCls));
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
People *aPeople = [People new];
NSLog(@"before release!");
printClassInfo(aPeople);
[aPeople release];
NSLog(@"after release!");
printClassInfo(aPeople);
}
return 0;
}
2凤巨、開啟Zombie Objects
3、運(yùn)行程序医窿,查看打印信息磅甩。從打印信息可以看到開啟僵尸對(duì)象檢測(cè)后,People釋放后所屬類變成了_NSZombie_People
姥卢。如此可得對(duì)象釋放后會(huì)變成僵尸對(duì)象卷要,保存當(dāng)前釋放對(duì)象的內(nèi)存地址,防止被系統(tǒng)回收独榴。
ZombieObjectDemo[1357:84410] before release!
ZombieObjectDemo[1357:84410] self:People - superClass:NSObject
ZombieObjectDemo[1357:84410] after release!
ZombieObjectDemo[1357:84410] self:_NSZombie_People - superClass:nil
4僧叉、結(jié)下來打開instruments ->Zombies ,查看dealloc 究竟做了什么。點(diǎn)擊運(yùn)行棺榔,查看Call trees瓶堕。結(jié)果如下圖,從dealloc的調(diào)用可以知道:Zombie Objects hook 住了對(duì)象的dealloc方法症歇,通過調(diào)用自己的__dealloc_zombie
方法來把對(duì)象進(jìn)行僵尸化郎笆。在Runtime源碼NSObject.mm文件中dealloc方法注釋中也有說明這一點(diǎn)。如下:
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
由對(duì)象dealloc方法調(diào)用棧( Call Tree )很好的驗(yàn)證了步驟3的打印信息忘晤。那么這個(gè)過程又是怎么樣的宛蚓?繼續(xù)探索。
4. Zombie Object的生成過程是怎么樣的
1设塔、創(chuàng)建__dealloc_zombie
符號(hào)斷點(diǎn)來看一探究竟
CoreFoundation`-[NSObject(NSObject) __dealloc_zombie]:
0x7fff3fa2dee7 <+23>: leaq 0x5a59c4a2(%rip), %rax ; __CFZombieEnabled
0x7fff3fa2defa <+42>: callq 0x7fff3fa7d930 ; symbol stub for: object_getClass
0x7fff3fa2df0a <+58>: callq 0x7fff3fa7d486 ; symbol stub for: class_getName
0x7fff3fa2df12 <+66>: leaq 0x237d1b(%rip), %rsi ; "_NSZombie_%s"
0x7fff3fa2df2b <+91>: callq 0x7fff3fa7d8b8 ; symbol stub for: objc_lookUpClass
0x7fff3fa2df38 <+104>: leaq 0x2376a9(%rip), %rdi ; "_NSZombie_"
0x7fff3fa2df3f <+111>: callq 0x7fff3fa7d8b8 ; symbol stub for: objc_lookUpClass
0x7fff3fa2df4d <+125>: callq 0x7fff3fa7d870 ; symbol stub for: objc_duplicateClass
0x7fff3fa2df61 <+145>: callq 0x7fff3fa7d86a ; symbol stub for: objc_destructInstance
0x7fff3fa2df6c <+156>: callq 0x7fff3fa7d948 ; symbol stub for: object_setClass
從此處斷點(diǎn)可以大概看出Zombie Object
的生成過程凄吏。_NSZombie_%s
驗(yàn)證了開啟僵尸對(duì)象檢測(cè)后的對(duì)象所指向的類。從這個(gè)調(diào)用棧也可以說明系統(tǒng)開啟僵尸對(duì)象檢測(cè)后不會(huì)釋放該對(duì)象所占用的內(nèi)存闰蛔,只是釋放了與該對(duì)象所有的相關(guān)引用痕钢。讓runtime源碼告訴你:
/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
上面是為開啟僵尸對(duì)象檢測(cè)對(duì)象釋放的調(diào)用過程,開啟僵尸對(duì)象檢測(cè)后將沒有 free(obj) 這一步的調(diào)用序六,而是執(zhí)行objc_destructInstance(obj)方法后就直接return了任连。我們也可以看看objc_destructInstance到底都干了些什么。從其注釋可以知道該方法做了下面幾件事:【C++ destructors】 【ARC ivar cleanup】 【Removes associative references】并沒有釋放其內(nèi)存难咕。
//***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
從匯編的調(diào)用順序可以大概總結(jié)出僵尸對(duì)象的生成過程课梳,如下:
//1距辆、獲取到即將deallocted對(duì)象所屬類(Class)
Class cls = object_getClass(self);
//2、獲取類名
const char *clsName = class_getName(cls)
//3暮刃、生成僵尸對(duì)象類名
const char *zombieClsName = "_NSZombie_" + clsName;
//4跨算、查看是否存在相同的僵尸對(duì)象類名,不存在則創(chuàng)建
Class zombieCls = objc_lookUpClass(zombieClsName);
if (!zombieCls) {
//5椭懊、獲取僵尸對(duì)象類 _NSZombie_
Class baseZombieCls = objc_lookUpClass(“_NSZombie_");
//6诸蚕、創(chuàng)建 zombieClsName 類
zombieCls = objc_duplicateClass(baseZombieCls, zombieClsName, 0);
}
//7、在對(duì)象內(nèi)存未被釋放的情況下銷毀對(duì)象的成員變量及關(guān)聯(lián)引用氧猬。
objc_destructInstance(self);
//8背犯、修改對(duì)象的 isa 指針,令其指向特殊的僵尸類
objc_setClass(self, zombieCls);
5. Zombie Object是如何被觸發(fā)的
1盅抚、再次調(diào)用[aPeople release] 可以看到程序斷在___forwarding___
漠魏,從此處的匯編代碼中可以看到關(guān)鍵字_NSZombie_
,在調(diào)用abort( )函數(shù)退出進(jìn)程時(shí)會(huì)有對(duì)應(yīng)的信息輸出@"*** -[%s %s]: message sent to deallocated instance %p"
妄均。所以可以大概猜出系統(tǒng)是在消息轉(zhuǎn)發(fā)過程中做了手腳柱锹。
CoreFoundation`___forwarding___:
0x7fff3f90b1cd <+269>: leaq 0x35a414(%rip), %rsi ; "_NSZombie_"
為此也可以大概總結(jié)出它的調(diào)用過程,如下:
//1丰包、獲取對(duì)象class
Class cls = object_getClass(self);
//2禁熏、獲取對(duì)象類名
const char *clsName = class_getName(cls);
//3、檢測(cè)是否帶有前綴_NSZombie_
if (string_has_prefix(clsName, "_NSZombie_")) {
//4邑彪、獲取被野指針對(duì)象類名
const char *originalClsName = substring_from(clsName, 10);
//5瞧毙、獲取當(dāng)前調(diào)用方法名
const char *selectorName = sel_getName(_cmd);
//6、輸出日志
Log(''*** - [%s %s]: message sent to deallocated instance %p", originalClsName, selectorName, self);
//7寄症、結(jié)束進(jìn)程
abort();
}
6. 結(jié)論
系統(tǒng)在回收對(duì)象時(shí)宙彪,可以不將其真的回收,而是把它轉(zhuǎn)化為僵尸對(duì)象有巧。這種對(duì)象所在的內(nèi)存無法重用您访,因此不可遭到重寫,所以將隨機(jī)變成必然剪决。
系統(tǒng)會(huì)修改對(duì)象的 isa 指針,令其指向特殊的僵尸類檀训,從而使該對(duì)象變?yōu)榻┦瑢?duì)象柑潦。僵尸類能夠相應(yīng)所有的選擇器,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接收者的消息峻凫,然后終止應(yīng)用程序渗鬼。