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

當(dāng)對象的引用計(jì)數(shù)為0時(shí), 系統(tǒng)會(huì)調(diào)用對象的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,最后到了NSObject的dealloc.

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

內(nèi)部做了一些判斷, 如果滿足這五個(gè)條件,直接調(diào)用free函數(shù),進(jìn)行內(nèi)存釋放.

當(dāng)一個(gè)最簡單的類(沒有任何成員變量,沒有任何引用的類),這五個(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)對象obj,再free(obj)釋放內(nèi)存.

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

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

objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        // 如果要釋放的對象沒有采用了優(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.
        // 如果要釋放的對象采用了優(yōu)化過的isa引用計(jì)數(shù),并且有弱引用或者使用了sideTable的輔助引用計(jì)數(shù)
        clearDeallocating_slow();
   }
    assert(!sidetable_present());
}

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

  1. 要釋放的對象沒有采用優(yōu)化過的isa引用計(jì)數(shù):

會(huì)調(diào)用sidetable_clearDeallocating() 函數(shù)做進(jìn)一步處理

void 
objc_object::sidetable_clearDeallocating()
{
    // 在全局的SideTables中,以this指針(要釋放的對象)為key,找到對應(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中找到對應(yīng)的引用計(jì)數(shù)表RefcountMap,拿到要釋放的對象的引用計(jì)數(shù)
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        //如果要釋放的對象被弱引用了,通過weak_clear_no_lock函數(shù)將指向該對象的弱引用指針置為nil
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        //從引用計(jì)數(shù)表中擦除該對象的引用計(jì)數(shù)
        table.refcnts.erase(it);
    }

    table.unlock();
}
  1. 如果該對象采用了優(yōu)化過的isa引用計(jì)數(shù)

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

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

void 

weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 

{
    //獲取被弱引用對象的地址
    objc_object *referent = (objc_object *)referent_id;
    // 根據(jù)對象地址找到被弱引用對象referent在weak_table中對應(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;
    
    // 找出弱引用該對象的所有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í)弱引用了對象 referent,則將weak指針設(shè)置為nil
            if (*referrer == referent) { 
                *referrer = nil;
            }
            // 如果所存儲的weak指針沒有弱引用對象 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修飾的對象在釋放時(shí), 所有弱引用該對象的指針都被設(shè)置為nil.
dealloc整個(gè)方法釋放流程如下圖:


img

看流程圖發(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)對象.(這也是為什么category添加屬性時(shí),在釋放時(shí)沒有必要remove)
  3. 就是上面寫的那個(gè),清空引用計(jì)數(shù)表并清除弱引用表,將weak指針置為nil
    object_cxxDestruct是由編譯器生成,這個(gè)方法原本是為了++對象析構(gòu),ARC借用了這個(gè)方法插入代碼實(shí)現(xiàn)了自動(dòng)內(nèi)存釋放的工作.

這個(gè)釋放.
現(xiàn)象:

  1. 當(dāng)類擁有實(shí)例變量時(shí),這個(gè)方法會(huì)出現(xiàn),且父類的實(shí)例變量不會(huì)導(dǎo)致子類擁有這個(gè)方法.
  2. 出現(xiàn)這個(gè)方法和變量是否被賦值,賦值成什么沒有關(guān)系.

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

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

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

  • 前言 我們知道一個(gè)對象即將釋放的時(shí)候會(huì)進(jìn)入到dealloc方法中,通常也是通過dealloc是否回調(diào)來檢測循環(huán)引用...
    初心丶可曾記閱讀 2,420評論 0 3
  • 我們知道weak是弱引用,指向的對象的計(jì)數(shù)器不會(huì)加一,并且在該對象釋放的時(shí)候會(huì)被置為nil。通常用來解決循環(huán)引用的...
    丶啊桑閱讀 769評論 0 1
  • 本文源自本人的學(xué)習(xí)記錄整理與理解,其中參考閱讀了部分優(yōu)秀的博客和書籍,盡量以通俗簡單的語句轉(zhuǎn)述。引用到的地方如有遺...
    水中的藍(lán)天閱讀 5,517評論 3 16
  • 我是前言 目前正在看 oc 底層的東西,看了許多大牛的博客,發(fā)現(xiàn)有一些小問題: runtime 的版本可能跟作者當(dāng)...
    kikido閱讀 775評論 0 1
  • 前言 今天我們大致分析下內(nèi)存管理相關(guān)的底層原理等知識點(diǎn),分為包括內(nèi)存布局和內(nèi)存管理方案兩大塊,其中內(nèi)存管理方案會(huì)重...
    深圳_你要的昵稱閱讀 524評論 0 2

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