597,iOS 野指針和僵尸對(duì)象,IOS系統(tǒng)中對(duì)僵尸對(duì)象是怎么做的?(面試點(diǎn):野指針:指針指向的對(duì)象已經(jīng)被回收掉了。這個(gè)指針就叫做野指針。 僵尸對(duì)象(Zombie Objects):僵尸對(duì)象一種...

野指針:指針指向的對(duì)象已經(jīng)被回收掉了。這個(gè)指針就叫做野指針。
僵尸對(duì)象:一個(gè)已經(jīng)被釋放的對(duì)象 就叫做僵尸對(duì)象

OC中是怎么實(shí)現(xiàn)的呢

首先如何打開Zombile Object 調(diào)試模式呢?

這個(gè)問題相信大家都不陌生:

第一步、打開Xcode 選擇屏幕左上角Xcode-> PReferencese

image.png

設(shè)置一下輸出信息,調(diào)試的時(shí)候能夠輸出更多的輸出信息

第二步再對(duì)環(huán)境變量進(jìn)行設(shè)置菜單 Product->Scheme->EditScheme

勾選上下列選項(xiàng):

image.png

這樣程序運(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ù)探索。

image.png

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é)論
  1. 系統(tǒng)在回收對(duì)象時(shí),可以不將其真的回收,而是把它轉(zhuǎn)化為僵尸對(duì)象。這種對(duì)象所在的內(nèi)存無法重用,因此不可遭到重寫,所以將隨機(jī)變成必然。

  2. 系統(tǒng)會(huì)修改對(duì)象的 isa 指針,令其指向特殊的僵尸類,從而使該對(duì)象變?yōu)榻┦瑢?duì)象。僵尸類能夠相應(yīng)所有的選擇器,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接收者的消息,然后終止應(yīng)用程序。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容