iOS Dealloc流程解析 Dealloc 實(shí)現(xiàn)原理

當(dāng)對(duì)象的引用計(jì)數(shù)為0時(shí), 系統(tǒng)會(huì)調(diào)用對(duì)象的dealloc方法釋放

- (void)dealloc {
    _objc_rootDealloc(self);
}

在內(nèi)部

void _objc_rootDealloc(id obj)
{
    assert(obj);
    obj->rootDealloc();
}

繼續(xù)調(diào)用了rootDealloc方法

顯然調(diào)用順序?yàn)?先調(diào)用當(dāng)前類的dealloc,然后調(diào)用父類的dealloc,最后到了NSObjectdealloc

inline void
objc_object::rootDealloc()
{
    //判斷對(duì)象是否采用了Tagged Pointer技術(shù)
    if (isTaggedPointer()) return;  // fixme necessary?
    //判斷是否能夠進(jìn)行快速釋放
    //這里使用了isa指針里的屬性來進(jìn)行判斷.
    if (fastpath(isa.nonpointer  &&  //對(duì)象是否采用了優(yōu)化的isa計(jì)數(shù)方式
                 !isa.weakly_referenced  &&  //對(duì)象沒有被弱引用
                 !isa.has_assoc  &&  //對(duì)象沒有關(guān)聯(lián)對(duì)象
                 !isa.has_cxx_dtor  &&  //對(duì)象沒有自定義的C++析構(gòu)函數(shù)
                 !isa.has_sidetable_rc  //對(duì)象沒有用到sideTable來做引用計(jì)數(shù)
                 ))
    {
        //如果以上判斷都符合條件,就會(huì)調(diào)用C函數(shù) ```free``` 將對(duì)象釋放
        assert(!sidetable_present());
        free(this);
    } 
    else {
        //如果以上判斷沒有通過,做下一步處理
        object_dispose((id)this);
    }
}

細(xì)數(shù)下判斷條件,fastpath封裝了編譯器指令,告訴編譯器內(nèi)部的條件大概率為真,減少調(diào)用時(shí)符號(hào)檢索的跳轉(zhuǎn)

對(duì)象采用了優(yōu)化的isa計(jì)數(shù)方式(isa.nonpointer)
對(duì)象沒有被weak引用!isa.weakly_referenced
沒有關(guān)聯(lián)對(duì)象!isa.has_assoc
沒有自定義的C++析構(gòu)方法!isa.has_cxx_dtor
沒有用到sideTable來做引用計(jì)數(shù) !isa.has_sidetable_rc

  • isa.nonpointer
    訪問對(duì)象的isa會(huì)直接返回一個(gè)指向cls的指針,是iPhone 遷移到64位系統(tǒng)之前時(shí)結(jié)構(gòu)

  • isa.nonpointer
    代表是已經(jīng)優(yōu)化的isa,類的信息存儲(chǔ)在shiftcls中,64位架構(gòu)都是優(yōu)化過的

內(nèi)部做了一些判斷, 如果滿足這五個(gè)條件,直接調(diào)用free函數(shù),進(jìn)行內(nèi)存釋放.
當(dāng)一個(gè)最簡(jiǎn)單的類(沒有任何成員變量,沒有任何引用的類),這五個(gè)判斷條件都是成立的,直接free

id object_dispose(id obj)
{
    if (!obj) return nil;
    
    objc_destructInstance(obj);    
    free(obj);
    
    return nil;
}

調(diào)用objc_destructInstance函數(shù)來析構(gòu)對(duì)象obj,再free(obj)釋放內(nèi)存.

objc_destructInstance內(nèi)部函數(shù)會(huì)銷毀C++析構(gòu)函數(shù)以及移除關(guān)聯(lián)對(duì)象的操作.

繼續(xù)調(diào)用objc_objectclearDeallocating函數(shù)做下一步處理

objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        // 如果要釋放的對(duì)象沒有采用了優(yōu)化過的isa引用計(jì)數(shù)
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        // 如果要釋放的對(duì)象采用了優(yōu)化過的isa引用計(jì)數(shù),并且有弱引用或者使用了sideTable的輔助引用計(jì)數(shù)
        clearDeallocating_slow();
   }
    assert(!sidetable_present());
}

根據(jù)是否采用了優(yōu)化過的isa做引用計(jì)數(shù)分為兩種:

  1. 要釋放的對(duì)象沒有采用優(yōu)化過的isa引用計(jì)數(shù):
    會(huì)調(diào)用sidetable_clearDeallocating()函數(shù)做進(jìn)一步處理
void objc_object::sidetable_clearDeallocating()
{
    // 在全局的SideTables中,以this指針(要釋放的對(duì)象)為key,找到對(duì)應(yīng)的SideTable
    SideTable& table = SideTables()[this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    //在散列表SideTable中找到對(duì)應(yīng)的引用計(jì)數(shù)表RefcountMap,拿到要釋放的對(duì)象的引用計(jì)數(shù)
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        //如果要釋放的對(duì)象被弱引用了,通過weak_clear_no_lock函數(shù)將指向該對(duì)象的弱引用指針置為nil
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        //從引用計(jì)數(shù)表中擦除該對(duì)象的引用計(jì)數(shù)
        table.refcnts.erase(it);
    }

    table.unlock();
}

2.如果該對(duì)象采用了優(yōu)化過的isa引用計(jì)數(shù)
并且該對(duì)象有弱引用或者使用了sideTable的輔助引用計(jì)數(shù),就會(huì)調(diào)用clearDeallocating_slow()函數(shù)做進(jìn)一步處理

NEVER_INLINE void

objc_object::clearDeallocating_slow()

{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    // 在全局的SideTables中,以this指針(要釋放的對(duì)象)為key,找到對(duì)應(yīng)的SideTable
    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        //要釋放的對(duì)象被弱引用了,通過weak_clear_no_lock函數(shù)將指向該對(duì)象的弱引用指針置為nil
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    //使用了sideTable的輔助引用計(jì)數(shù),直接在SideTable中擦除該對(duì)象的引用計(jì)數(shù)
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

以上兩種情況都涉及weak_clear_no_lock函數(shù), 它的作用就是將被弱引用對(duì)象的弱引用指針置為nil.

void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 

{
    //獲取被弱引用對(duì)象的地址
    objc_object *referent = (objc_object *)referent_id;
    // 根據(jù)對(duì)象地址找到被弱引用對(duì)象referent在weak_table中對(duì)應(yīng)的weak_entry_t
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); 
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }
    
    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    // 找出弱引用該對(duì)象的所有weak指針地址數(shù)組
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    // 遍歷取出每個(gè)weak指針的地址
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i]; 
        if (referrer) {
            // 如果weak指針確實(shí)弱引用了對(duì)象 referent,則將weak指針設(shè)置為nil
            if (*referrer == referent) { 
                *referrer = nil;
            }
            // 如果所存儲(chǔ)的weak指針沒有弱引用對(duì)象 referent,這可能是由于runtime代碼的邏輯錯(cuò)誤引起的,報(bào)錯(cuò)
            else if (*referrer) { 
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    weak_entry_remove(weak_table, entry);
}

這里也表明了為什么被weak修飾的對(duì)象在釋放時(shí), 所有弱引用該對(duì)象的指針都被設(shè)置為nil.
dealloc整個(gè)方法釋放流程如下圖:

看流程圖發(fā)現(xiàn),如果五個(gè)條件不滿足.內(nèi)存無法進(jìn)行快速釋放.在上面中,我看到博客里關(guān)于objc_destructInstance 這個(gè)方法只是概述而過,所以我找了相關(guān)資料來了解一下.

void *objc_destructInstance(id obj) 
{
    if (obj) {
        Class isa_gen = _object_getClass(obj);
        class_t *isa = newcls(isa_gen);


        // Read all of the flags at once for performance.
        bool cxx = hasCxxStructors(isa);
        bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);


        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        
        if (!UseGC) objc_clear_deallocating(obj);
    }
    return obj;
}

總共干了三件事::

  1. 執(zhí)行了object_cxxDestruct 函數(shù)
  2. 執(zhí)行_object_remove_assocations,去除了關(guān)聯(lián)對(duì)象.(這也是為什么category添加屬性時(shí),在釋放時(shí)沒有必要remove)
  3. 就是上面寫的那個(gè),清空引用計(jì)數(shù)表并清除弱引用表,將weak指針置為nil
    object_cxxDestruct是由編譯器生成,這個(gè)方法原本是為了C++對(duì)象析構(gòu),ARC借用了這個(gè)方法插入代碼實(shí)現(xiàn)了自動(dòng)內(nèi)存釋放的工作.
    這個(gè)釋放現(xiàn)象:
  4. 當(dāng)類擁有實(shí)例變量時(shí),這個(gè)方法會(huì)出現(xiàn),且父類的實(shí)例變量不會(huì)導(dǎo)致子類擁有這個(gè)方法.
  5. 出現(xiàn)這個(gè)方法和變量是否被賦值,賦值成什么沒有關(guān)系.

所以, 我們可以認(rèn)為這個(gè)方法就是用來釋放該類中的屬性的. weak修飾的屬性應(yīng)該不包含在內(nèi).

總結(jié)

對(duì)象的引用計(jì)數(shù)為0時(shí)會(huì)執(zhí)行dealloc函數(shù),調(diào)用棧如下:
dealloc->_objc_rootDealloc->object_dispose->objc_destructInstance
objc_destructInstance函數(shù)內(nèi)部會(huì)依次調(diào)用c++析構(gòu)函數(shù)object_cxxDestruct、關(guān)聯(lián)對(duì)象析構(gòu)函數(shù)_object_remove_assocations、弱引用析構(gòu)函數(shù)clearDeallocating

最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 今天青石的票圈出鏡率最高的,莫過于張藝謀的新片終于定檔了。 一張滿溢著水墨風(fēng)的海報(bào)一次次的出現(xiàn)在票圈里,也就是老謀...
    青石電影閱讀 10,990評(píng)論 1 2
  • 一、jQuery簡(jiǎn)介 JQ是JS的一個(gè)優(yōu)秀的庫,大型開發(fā)必備。在此,我想說的是,JQ里面很多函數(shù)使用和JS類似,所...
    Welkin_qing閱讀 12,920評(píng)論 1 6
  • 字符串 1.什么是字符串 使用單引號(hào)或者雙引號(hào)括起來的字符集就是字符串。 引號(hào)中單獨(dú)的符號(hào)、數(shù)字、字母等叫字符。 ...
    mango_2e17閱讀 7,834評(píng)論 1 7
  • 一場(chǎng)說走就走的旅行。 簡(jiǎn)單地整理幾件必備旅行用品,7.18日早上8.00準(zhǔn)時(shí)出發(fā)了,計(jì)劃一路南下然后繞西南一圈回恩...
    悠游魚閱讀 3,680評(píng)論 3 6
  • 原來看過一段話,第一厲害的人有能力沒脾氣,第二厲害的人有能力有脾氣,最差的是沒能力有脾氣的人。 以我最...
    涼風(fēng)豆豆閱讀 1,637評(píng)論 2 0

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