內存管理(二)

ARC&MRC主要與alloc,retain,realese,retainCountautorealese,dealloc這些方法的調用有關。

ARC:是LLVM和Runtime配合的結果,ARC中禁止使用手動調用reatain/release/retainCount/dealloc,ARC新加了weak,strong屬性關鍵字。

這里穿插一個問題:

FXTank *p = [[FXTank alloc] init];
NSLog(@"%lu", (unsigned long)[p retainCount]);

這段代碼輸出多少?(首先需要知道引用計數(shù)存在isa.bits. extra_rc或者散列表)

打印如下:

為啥輸出1,從源碼來找答案:

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

進入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();
}

總結:從這里可以看到,uintptr_t rc = 1 + bits.extra_rc;引用計數(shù)手動加1(其中bit.extra_rc打印為nil),如果bits.has_sidetable_rc哈希表沒有引用計數(shù),就直接返回1,所以前面的的答案是1,至于這里為啥為1,原因是如果為0,就直接析構了釋放了。

下面繼續(xù)測試:

FXTank *p = [[FXTank alloc] init];
[p retain]; //這里的引用計數(shù)+1
NSLog(@"%lu", (unsigned long)[p retainCount]);

打印如下:


分析:retain是加到extrac,因為本來retainCount就會1+extra_rc,這里extra_rc由加了1,所以輸出結果為2。
查看retain源碼:

// Replaced by ObjectAlloc
- (id)retain {
    return ((id)self)->rootRetain();
}

進入rootRetain

// Base retain implementation, ignoring overrides.
// This does not check isa.fast_rr; if there is an RR override then 
// it was already called and it chose to call [super retain].
//
// tryRetain=true is the -_tryRetain path.
// handleOverflow=false is the frameless fast path.
// handleOverflow=true is the framed slow path including overflow to side table
// The code is structured this way to prevent duplication.

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

進入rootRetain:

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this; //TaggedPointer是不會走下面retain,release,不會使用到引用計數(shù)的加減操作

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        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);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            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)) {
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}

分析:首先整體是一個do-while循環(huán),這里while判斷是引用計數(shù)是否儲備完整,如果沒有儲備完整,就一直在do循環(huán)里面處理ARC的引用計數(shù)。newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ 這段代碼就是用來引用計數(shù)+1的,RC_ONE(宏定義 )就是1,# define RC_ONE (1ULL<<56),為什么是左移動56位呢?其實在上篇博客里面提到過,在ISA_BITFIELD宏定義里面使用了聯(lián)合體,其中uintptr_t extra_rc : 8的意思就是代表占用了64位中的后8位,所以這里的1要加到后8位中,左移56位代表的就是后8位中的1。

newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true; //標記需要存哈希表
sidetable_addExtraRC_nolock(RC_HALF);

這段代碼的意思是,如果extra_rc存滿了,就取出一半存到SideTable哈希表里面的RefcountMap refcnts;引用計數(shù)map里面。
注意:其中# define RC_HALF (1ULL<<7)為8位的一半,差一個最高位就是二進制差一半。

進入sidetable_addExtraRC_nolock方法

// Move some retain counts to the side table from the isa field.
// Returns true if the object is now pinned.
bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this]; //取出響應的散列表

    size_t& refcntStorage = table.refcnts[this];
    size_t oldRefcnt = refcntStorage;
    // isa-side bits should not be set here
    assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    uintptr_t carry;
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry); //引用計數(shù)+1
    if (carry) {
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        refcntStorage = newRefcnt;
        return false;
    }
}

分析:refcnts中取出散列表,addc方法引用計數(shù)+1。
注意這里的SIDE_TABLE_RC_SHIFT為2,因為這里是前兩位做其他用途,所以第2位開始,和前面有所不同。(如果散列表也存滿了,就會處理引用計數(shù)傳存儲,這里不再深入探討了)

總結:回到最開始的引用計數(shù)打印,就很明確了,先取bits. extra_rc里面的引用計數(shù),然后判斷has_sidetable_rc里面是否有引用計數(shù),最后相加返回最終的引用計數(shù)個數(shù)。

接下來我們在試下下面這段代碼:

FXTank *p = [[FXTank alloc] init];
[p retain];
NSLog(@"%lu", (unsigned long)[p retainCount]);
[p release];
NSLog(@"%lu", (unsigned long)[p retainCount]);

下面來看下release方法源碼:

// Replaced by ObjectAlloc
- (oneway void)release {
    ((id)self)->rootRelease();
}

進入rootRelease方法:

// Base release implementation, ignoring overrides.
// Does not call -dealloc.
// Returns true if the object should now be deallocated.
// This does not check isa.fast_rr; if there is an RR override then 
// it was already called and it chose to call [super release].
// 
// handleUnderflow=false is the frameless fast path.
// handleUnderflow=true is the framed slow path including side table borrow
// The code is structured this way to prevent duplication.

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

進入rootRelease方法:

ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    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)) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

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

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;

    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.

        if (!sideTableLocked) {
            ClearExclusive(&isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            goto retry;
        }

        // Try to remove some retain counts from the side table.        
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.

        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if (!stored) {
                // Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

    // Really deallocate.

    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __sync_synchronize();
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); //發(fā)送析構消息
    }
    return true;
}

分析:前面引用計數(shù)-1和+1類似,這里也有array溢出,只不過是下溢出,就是減到0繼續(xù)減,下溢出之后會調用underflow,判斷SideTable是否有引用計數(shù),如果有就從散列表里面borrowed借用RC_HALF-1(也就是RC_HALF一半),然后在調用retry遞歸繼續(xù)引用計數(shù)-1,如果散列表里面也沒有了,調用析構方法,給this對象發(fā)SEL_dealloc消息。

注意:這里有一個問題,在對象剛創(chuàng)建的時候引用計數(shù)為0,但是不會走析構函數(shù),只有在調用release函數(shù)把引用計數(shù)減到0的時候才會調用析構函數(shù),也就是說引用計數(shù)為0是調用析構函數(shù)必要條件,但不是充分條件,必須要有一個release觸發(fā)的過程。

引用計數(shù)總結:isa是一個NONPOINTER_ISA,他是按位存儲的。我們的引用計數(shù)存儲是有兩個位置的,第一個是在isa.bits.extra_rc,第二個是在散列表里面,retain是會進行extra_rc+1返回的,extra_rc只有8給位置,會發(fā)送carry上溢出,就會吧一半的內容放到散列表里面,如果散列表也滿了,會調整容量結構。release會進行extra_rc-1,如果extra_rc為0,就會發(fā)生下溢出,下溢出會判斷散列表,然后借用一半存到extra_rc里面,如果散列表里面也沒有,就會調用這個對象的析構函數(shù)。

疑問:這里有一個問題就是為什么要把extra_rc中的一半放到散列表里面,而不是全部?原因是不能過于平凡的操作散列表,如果全部都放到散列表里面,每次release的時候都會訪問散列表,雖然extra_rc操作簡單了,但是散列表的操作增多了,這樣對于性能不是很好(感覺一半的話有點像二分法的思想,玄學)。

接下來我們看下Dealloc,首先是應該在Dealloc函數(shù)里面做什么事情?(底層實現(xiàn))

  1. free函數(shù),釋放相關對象。
  2. weak引用計數(shù)表需要做處理。
  3. 關聯(lián)對象associated對象需要釋放。

首先找到源碼:

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}

進入_objc_rootDealloc:

void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}

繼續(xù)進入rootDealloc:

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    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);
    }
}

分析:首先判斷是否是nonpointer(小類型特殊處理),weakly_referenced(弱引用),has_assoc(關聯(lián)對象),has_cxx_dtor(c++),has_sidetable_rc(散列表引用計數(shù)),如果不是直接free,否則走一般對象釋放流程object_dispose。

繼續(xù)進入object_dispose:

/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);   //處理對象,為釋放做準備
    free(obj); //處理完成后釋放

    return nil;
}

分析:主要是objc_destructInstance為釋放做準備,然后直接free釋放對象。

繼續(xù)進入objc_destructInstance:

/***********************************************************************
* 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;
}

分析:如果有c++的析構函數(shù)就先調用c++析構,如果有關聯(lián)對象的就移除。

然后進入clearDeallocating方法:

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        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.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

繼續(xù)進入sidetable_clearDeallocating:

void 
objc_object::sidetable_clearDeallocating()
{
    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();
    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();
}

分析:如果it不再table.refcnts.end,就是說it不是最后一個的話,就一個一個從頭開始往后遍歷移除,因為對象已經不存在了,所以然后就是table.refcnts.erase(it);引用計數(shù)移除。

進入weak_clear_no_lock方法:

/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated. 
 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    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;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            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);
}

分析:首先拿到weaktable里面的entry,然后再取出entry里面的referrers,然后for循環(huán)遍歷referrers,如果referrer存在,直接置為nil,回收內存。(這里解釋了經常說的weak一旦被釋放,就會職位nil,不存在所謂的循環(huán)引用,因為它是一個nil對象),因為referrers已經空了,所以entry存在的意義也就沒有了,所以調用weak_entry_remove(方法內部還有移除容量等)移除entry

析構總結:(總流程圖)
后面是補充的weak原理:

weak原理:

NSObject *obj = [[NSObject alloc] init];
id __weak obj2 = obj;

weak修飾變量在斷點處可以看到調用objc_initWeak方法,如下:

下面我們在源碼中查看objc_initWeak這個方法:

/** 
 * Initialize a fresh weak pointer to some object location. 
 * It would be used for code like: 
 *
 * (The nil case) 
 * __weak id weakPtr;
 * (The non-nil case) 
 * NSObject *o = ...;
 * __weak id weakPtr = o;
 * 
 * This function IS NOT thread-safe with respect to concurrent 
 * modifications to the weak variable. (Concurrent weak clear is safe.)
 *
 * @param location Address of __weak ptr. 
 * @param newObj Object ptr. 
 */
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

繼續(xù)進入storeWeak:

// Update a weak variable.
// If HaveOld is true, the variable has an existing value 
//   that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be 
//   assigned into the variable. This value might be nil.
// If CrashIfDeallocating is true, the process is halted if newObj is 
//   deallocating or newObj's class does not support weak references. 
//   If CrashIfDeallocating is false, nil is stored instead.
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
    assert(haveOld  ||  haveNew);
    if (!haveNew) assert(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

分析:這里的重點是,在添加新的weak對象的時候,先要判斷是否存在舊值,如果存在舊要先移除舊的,然后調用weak_register_no_lock添加新的weak對象,下面進入weak_register_no_lock方法繼續(xù)分析源碼。

進入weak_register_no_lock方法:

/** 
 * Registers a new (object, weak pointer) pair. Creates a new weak
 * object entry if it does not exist.
 * 
 * @param weak_table The global weak table.
 * @param referent The object pointed to by the weak reference.
 * @param referrer The weak pointer address.
 */
id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }

    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }

    // now remember it and where it is being stored
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        weak_entry_t new_entry(referent, referrer); //創(chuàng)建新entry
        weak_grow_maybe(weak_table); //空間是否要擴容
        weak_entry_insert(weak_table, &new_entry); //插入新entry
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

分析:這里有兩個邏輯,第一個是如果有entry就調用append_referrer加入到weak_table里面,如果weak表里面沒有entry就創(chuàng)建新weak_entry_t的,然后加入到weak_table里面。

繼續(xù)進入weak_entry_for_referent:

/** 
 * Return the weak reference table entry for the given referent. 
 * If there is no entry for referent, return NULL. 
 * Performs a lookup.
 *
 * @param weak_table 
 * @param referent The object. Must not be nil.
 * 
 * @return The table of weak referrers to this object. 
 */
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}

分析:這里的結構是一張哈希表,while循環(huán)一直找到和referent的key值不一樣的index然后從返回 &weak_table->weak_entries[index] (entry地址),返回的&entry就會調用下面的append_referrer方法添加到weak_table里面。

/** 
 * Add the given referrer to set of weak pointers in this entry.
 * Does not perform duplicate checking (b/c weak pointers are never
 * added to a set twice). 
 *
 * @param entry The entry holding the set of weak pointers. 
 * @param new_referrer The new weak pointer to be added.
 */
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) {
        // Try to insert inline.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

        // Couldn't insert inline. Allocate out of line.
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }

    assert(entry->out_of_line());

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
    }
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

weak總結下:

結構流程:
sidetabless -> sidetable -> 弱引用表
weakTable -> entry - >數(shù)組 ->弱引用對象指針

存儲流程:

  1. weak_entry *weak_entries 取出entry
  2. entry->inline_referres[i] = new_referrer
  3. new_referrers[i] = entry->inline_referrers[i]
  4. entry->referrers = new_referrers

注意:這里weak是沒有操作引用計數(shù)的。

流程圖示:

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

相關閱讀更多精彩內容

  • iOS中內存管理機制是開發(fā)中一項很重要的知識,了解iOS中內存管理的規(guī)則不管是在開發(fā)中還是在學習中都能很大程度的幫...
    Mr_Atom閱讀 3,485評論 1 4
  • 1.內存布局 棧區(qū) 0x7創(chuàng)建臨時變量時由編譯器自動分配,在不需要的時候自動清除的變量的存儲區(qū)。里面的變量通常是局...
    沒戲還在演戲閱讀 328評論 0 0
  • 轉載:http://www.itdecent.cn/p/ef6d9bf8fe59 通過閱讀本文你可以了解iOS管...
    高思陽閱讀 725評論 0 0
  • 上世紀中期,由于生存環(huán)境的惡劣,造就了不少奇葩。他們正帶著一顆扭曲的心靈,生活在我們的身邊。 沒錯,我說的就是他。...
    天云lty閱讀 225評論 3 4
  • 網上有時候也有好玩的事情,今天在微博上看到有這么一件有趣的事情。 某人將洗好的枕頭,毛絨玩具熊,和小毯子,晾曬在室...
    小蟻沖沖沖閱讀 416評論 0 2

友情鏈接更多精彩內容