iOS-內存管理(二)-引用計數

Objective-C內存管理的核心思想就是經過對象的引用計數來對內存對象的生命週期進行控制。說直白一點,就是調用retain會加1,調用release就會減1,引用計數清零或者調用dealloc就銷燬。c++

引用計數

引用計數,即爲對象被持有的次數。是內存管理的核心點。下面咱們來看一個關於引用計數的例子:安全

- (void)testRefCount {
    NSObject *obj = [NSObject alloc];
    NSLog(@"==refCount==%ld==", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
}
複製代碼

運行程序,結果爲1。但是alloc的流程中並無對引用計數操做的流程,那麼這個打印爲何是1呢?來看看retainCount的源碼:ide

- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}
複製代碼

能夠看到,引用計數的總值就是isa裏面extra_rc的取值和散列表中引用計數表的取值外加1,咱們新alloc的對象,引用計數打印爲1就是由於這個加的這個1,其實新alloc出來的對象引用計數爲0。函數

那麼引用計數是存儲在哪裏的,retainrelease究竟是如何處理的呢?下面咱們先來看一下retainpost

retain

id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

inline id 
objc_object::retain()
{
    assert(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        return rootRetain();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
複製代碼

能夠看到,調用objc_retain首先會判斷是不是isTaggedPointer,若是是就直接返回。接着會判斷對象沒有自定義retain/release方法就會調用rootRetain,不然就經過objc_msgSend發送SEL_retain消息。性能

id 
objc_object::rootRetain()
{
    return rootRetain(false, false);
}

id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        // 獲取isa
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            // 不是nonpointer isa 散列表處理
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // 若是正在deallocating 不作處理
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        
        // 是nonpointer isa  extra_rc++
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // 超出以後一半存到散列表中,一半放在extra_rc 而且處理isa的extra_rc標誌位和has_sidetable_rc
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}
複製代碼

進入到rootRetain會進行以下操做:ui

    1. 判斷是不是isTaggedPointerTaggedPointer是不須要維護引用計數的,直接返回。
    1. 不是TaggedPointer,就獲取對象的isa,判斷是否是nonpointer isa
    1. 不是nonpointer isa,交給散列表處理,對引用計數進行++操做,而後返回
    1. 判斷是否是正在deallocating,是的話直接返回
    1. nonpointer isa,對isa的標誌位extra_rc執行++操做
    1. 若是計數超出extra_rc能存儲的範圍了,就將其中的一半存在extra_rc,並把has_sidetable_rc標誌位置爲1。而後拷貝另一半放入散列表進行保存。

散列表是多張表,因爲性能和安全的考慮,是多張而不是一張,可是多張並非每一個對象就一張。散列表的存儲引用計數的方式以下,也就是對存儲的引用計數進行++操做:this

bool
objc_object::sidetable_tryRetain()
{
    SideTable& table = SideTables()[this];

    bool result = true;
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
        table.refcnts[this] = SIDE_TABLE_RC_ONE;
    } else if (it->second & SIDE_TABLE_DEALLOCATING) {
        result = false;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
        it->second += SIDE_TABLE_RC_ONE;
    }
    
    return result;
}

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];
    
    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}
複製代碼

release

retain是引用計數+1,而release是引用計數-1,流程是相輔相成的。調用release仍是會進入到objc_release方法。spa

void 
objc_release(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}

inline void
objc_object::release()
{
    assert(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        rootRelease();
        return;
    }

    ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}
複製代碼

能夠看到,調用objc_release也會先判斷是不是isTaggedPointer,若是是就直接返回。接着會判斷對象沒有自定義retain/release方法就會調用rootRelease,不然就經過objc_msgSend發送SEL_release消息。3d

bool 
objc_object::rootRelease()
{
    return rootRelease(true, false);
}

objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    // isTaggedPointer 不作處理 直接返回
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            // 不是nonpointer_isa 對散列表中的引用計數進行處理
            ClearExclusive(&isa.bits);
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // nonpointer_isa 對isa的extra_rc 進行--操做
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  
        if (slowpath(carry)) {
            // 若是不夠減,則須要去散列表的引用計數表中借位
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    newisa = oldisa;
    
    // 此時isa的extra_rc已經清零 沒有計數了
    // isa的散列表引用標誌位有值
    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
            ClearExclusive(&isa.bits);
            // 遞歸調用
            return rootRelease_underflow(performDealloc);
        }

        if (!sideTableLocked) {
            ClearExclusive(&isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            
            // 散列表沒加鎖,加鎖 遞歸
            goto retry;
        }

        // 把散列表裏存儲的引用計數取出來   
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // 散列表中的引用計數若是大於0
        if (borrowed > 0) {
            // 對散列表的引用計數作--操做 而後存入isa的extra_rc
            newisa.extra_rc = borrowed - 1;  
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if (!stored) {
                // 若是沒有成功的存入 isa的extra_rc 那就再存一遍
                ......
            }

            if (!stored) {
                // 二次存入仍是沒有成功 就把數據放回到散列表的引用計數表
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            // 從散列表借位--成功
            sidetable_unlock();
            return false;
        }
        else {
            // 散列表的引用計數也是空的
        }
    }

    // isa 沒有在deallocating中 那拋出異常
    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
    }
    // 將isa置爲deallocating,而後再來遞歸一遍
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();
    
    __sync_synchronize();
    if (performDealloc) {
        // 發送一個SEL_dealloc的消息
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}
複製代碼

梳理一下,進入到rootRelease以後的流程以下:

    1. 判斷是不是isTaggedPointerTaggedPointer是不須要維護引用計數的,直接返回。
    1. 不是TaggedPointer,就獲取對象的isa,判斷是否是nonpointer isa
    1. 不是nonpointer isa,交給散列表處理,對引用計數進行--操做,若是散列表的引用計數清零,就須要對該對象發送SEL_dealloc信息,執行dealloc操做,而後返回。
    1. 若是是nonpointer_isa就對isaextra_rc進行--操做,當extra_rc計數爲0,不夠減的時候,就須要從散列表的引用計數表借位減。
    1. 判斷isahas_sidetable_rc是否有值,有值就進行第6步,沒有值就進行第8步
    1. 獲取散列表的引用計數,若是計數等於0,就對該對象發送SEL_dealloc信息,執行dealloc操做。
    1. 若是從散列表獲取的引用計數大於0,將計數減1,而後存入isaextra_rc,返回。若是沒有存入成功,則進行2次遞歸存儲,若是仍是沒有成功,就將計數存入散列表,繼續進行一次遞歸操做。
    1. 判斷isa是否正處於deallocating,若是沒有那就拋出異常。
    1. 對該對象發送SEL_dealloc信息,執行dealloc操做。

散列表的引用計數表release引用計數的操做:

uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
        do_dealloc = true;
        it->second |= SIDE_TABLE_DEALLOCATING;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
        it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}
複製代碼

dealloc

當頁面銷燬或者對象銷燬的時候就會進入dealloc方法進行相關的處理。

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

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

inline void
objc_object::rootDealloc()
{
    // TaggedPointer 不用處理引用計數
    if (isTaggedPointer()) return;  // fixme necessary?
    
    // 是nonpointer
    // 沒有弱引用表、沒有關聯對象、沒有c++析構器、沒有散列表引用計數
    // 直接釋放
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return 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();

        // 存在c++析構函數 調用析構
        if (cxx) object_cxxDestruct(obj);
        // 存在關聯對象 刪除關聯對象
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // 非non-pointer isa
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // non-pointer isa
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

void 
objc_object::sidetable_clearDeallocating()
{
    SideTable& table = SideTables()[this];

    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            // 清除弱引用表
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        // 清除散列表中的引用計數表的相關信息
        table.refcnts.erase(it);
    }
    table.unlock();
}

void
objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    // 清除弱引用表
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    // 清除散列表中的引用計數表的相關信息
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}
複製代碼

總結一下,調用dealloc方法的流程:

    1. 若是是TaggedPointer,則不用處理引用計數,返回
    1. 若是是nonpointer_isa,且沒有弱引用表、沒有關聯對象、沒有c++析構器、沒有散列表引用計數,那就直接釋放,不然進入第3步,調用object_dispose()
    1. 調用objc_destructInstance(obj),而後在調用free(obj)
    1. objc_destructInstance()方法中,判斷若是存在cxx析構器則須要調用析構方法,若是存在關聯對象須要刪除關聯對象。
    1. 清除弱引用表中的相關信息,清除散列表中引用計數表的信息。

弱引用表的釋放詳見weak原理,此處就不贅述。

總結

retain流程

release流程

dealloc流程

相關文章
相關標籤/搜索