iOS-内存管理分析(中)

前言

iOS-内存管理分析(上)一文我们分析了内存的五大区,taggedPointer,retain,release的底层分析, 这篇文章继续分析内存管理的其它相关知识。

1 散列表结构分析

散列表到底是什么东西,它有什么作用,我们现在来分析下。
散列表其实就是哈希表。只是对当前表的名字
首先我们在objc的源码中搜索一下rootRetain,找到如下代码

objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    if (slowpath(isTaggedPointer())) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    oldisa = LoadExclusive(&isa.bits);

    if (variant == RRVariant::FastOrMsgSend) {
        // These checks are only meaningful for objc_retain()
        // They are here so that we avoid a re-load of the isa.
        if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
            ClearExclusive(&isa.bits);
            if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
                return swiftRetain.load(memory_order_relaxed)((id)this);
            }
            return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
        }
    }

    if (slowpath(!oldisa.nonpointer)) {
        // a Class is a Class forever, so we can perform this check once
        // outside of the CAS loop
        if (oldisa.getDecodedClass(false)->isMetaClass()) {
            ClearExclusive(&isa.bits);
            return (id)this;
        }
    }

    do {
        transcribeToSideTable = false;
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain(sideTableLocked);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            if (slowpath(tryRetain)) {
                return nil;
            } else {
                return (id)this;
            }
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (variant != RRVariant::Full) {
                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 (variant == RRVariant::Full) {
        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();
    } else {
        ASSERT(!transcribeToSideTable);
        ASSERT(!sideTableLocked);
    }

    return (id)this;
}
  • sidetable_tryRetain*我们看下这个函数的源码,所示
bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    // NO SPINLOCK HERE
    // _objc_rootTryRetain() is called exclusively by _objc_loadWeak(), 
    // which already acquired the lock on our behalf.

    // fixme can't do this efficiently with os_lock_handoff_s
    // if (table.slock == 0) {
    //     _objc_fatal("Do not call -_tryRetain.");
    // }

    bool result = true;
    auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE);
    auto &refcnt = it.first->second;
    if (it.second) {
        // there was no entry
    } else if (refcnt & SIDE_TABLE_DEALLOCATING) {
        result = false;
    } else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
        refcnt += SIDE_TABLE_RC_ONE;
    }
    
    return result;
}

这里调用了SideTable& table = SideTables()[this];这个SideTables函数,散列表是有多张的(真机8张表,模拟器 6张表)基本结构如下

template<>
void SideTable::unlockTwo
    (SideTable *, SideTable *lock2)
{
    lock2->unlock();
}

static objc::ExplicitInit> SideTablesMap;

static StripedMap& SideTables() {
    return SideTablesMap.get();
}

// anonymous namespace
};

void SideTableLockAll() {
    SideTables().lockAll();
}

void SideTableUnlockAll() {
    SideTables().unlockAll();
}

void SideTableForceResetAll() {
    SideTables().forceResetAll();
}

void SideTableDefineLockOrder() {
    SideTables().defineLockOrder();
}

void SideTableLocksPrecedeLock(const void *newlock) {
    SideTables().precedeLock(newlock);
}

void SideTableLocksSucceedLock(const void *oldlock) {
    SideTables().succeedLock(oldlock);
}

void SideTableLocksPrecedeLocks(StripedMap& newlocks) {
    int i = 0;
    const void *newlock;
    while ((newlock = newlocks.getLock(i++))) {
        SideTables().precedeLock(newlock);
    }
}

void SideTableLocksSucceedLocks(StripedMap& oldlocks) {
    int i = 0;
    const void *oldlock;
    while ((oldlock = oldlocks.getLock(i++))) {
        SideTables().succeedLock(oldlock);
    }
}

为什么散列表是多张表,而不是一张表?
我们往下分析,我们看下SideTable是什么,找到其源码。


struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

spinlock_t是把锁
RefcountMap是引用表计数表,我们上篇文章提到的extra_rc就存在这里
weak_table_t是弱引用计数表,比如被__weak修饰的
如果在系统中共用一张表的话,就会大大影响性能(每次需要开锁,解锁),多张表的可以即时回收内存(一张表的数据都置空,这张表就可以回收)
sidetable_retain这个函数源码如下

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

    return (id)this;
}

这里获取对象所在的表,并根据这张表获取到对象所在的存储空间,对其进行++操作。
sidetable_release源码

uintptr_t
objc_object::sidetable_release(bool locked, bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    if (!locked) table.lock();
    auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
    auto &refcnt = it.first->second;
    if (it.second) {
        do_dealloc = true;
    } else if (refcnt < SIDE_TABLE_DEALLOCATING) {
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        refcnt |= SIDE_TABLE_DEALLOCATING;
    } else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
        refcnt -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return do_dealloc;
}

2 引用计数分析

 NSObject *objc = [[NSObject alloc] init];
        __weak typeof (id) weakObjc = objc;
        NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);

我们经常会有这样的写法,通过__weak修饰变量,这个weakObjc会被加入弱引用表中。
我们在源码中搜下retainCount找到以下源码

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

    sidetable_lock();
    isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
    if (bits.nonpointer) {
        uintptr_t rc = bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

在这里拿到isa的bits,判断如果是nonpointer,这里不会执行

if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }

因为extra_rc还未存满,所以不会进入散列表,CFGetRetainCount这个函数打印1是因为在isa初始化的默认赋值1。
我们调整下代码,如下

  NSObject *objc = [[NSObject alloc] init];
        NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);
        __weak typeof (id) weakObjc = objc;
        NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);
        NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),objc);

这时候运行项目会出现什么情况,我们试下,如图


1

打印结果1,1,2。
这是为什么?
我们加入弱引用表是不影响引用计数的,为什么打印weakObjc是2呢?我们要想知道这些,我们就得分析弱引用表。

3 弱引用表

我们要分析__weak的原理是什么,就要探索一下,在我们刚才的代码上,先运行项目,如图

1

这里发现进到源码中了,在objc_initWeak这个函数中,因为我们是通过源码编译分析的,如果不是通过源码分析,也可以通过汇编一步一步的分析流程。
我们在源码中搜索objc_initWeak这个函数,如下

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak
        (location, (objc_object*)newObj);
}

这也说明了,只要使__weak修饰,就会进入到objc_initWeak这个函数,也就是说__weak这个标示符与objc_initWeak有绑定关系, 我们可以在llvm中找到相应的绑定关系,如图

2

这里可以看出__weakOCL_Weak的对应关系,OCL_Weak就是objc_initWeak,如图

3

4

GetFunctionClass会返回要调用的函数,如果是__weak类型就会调用objc_initWeak这个函数。
objc_initWeak调用storeWeak传的是(objc_object)newObj,而objc_destroyWeak*释放函数这里传的是nil,

storeWeak
        (location, (objc_object*)newObj);

location是weakSelf指针地址,(objc_object)newObj就是要绑定的对象,我们接着看下storeWeak*的源码,如下

template 
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(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo(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(oldTable, newTable);
            class_initialize(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 ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (!newObj->isTaggedPointerOrNil()) {
            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(oldTable, newTable);

    // This must be called without the locks held, as it can invoke
    // arbitrary code. In particular, even if _setWeaklyReferenced
    // is not implemented, resolveInstanceMethod: may be, and may
    // call back into the weak reference machinery.
    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}

这段代码

 if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }

判断在弱引用表查找,有没有这个对象,因为第一次进来,肯定没有,所以oldTable为nil。
接着

if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

这里判断能不能找到原来的对象,第一次进来,肯定是可进来的,我们调试下,如图


5

然后再执行

   if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

如果是旧的,移除。
如果是新的就注册进来:

    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo(oldTable, newTable);
            class_initialize(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;
        }
    }

如果新的对象进来,执行这里的代码。
再接着

    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

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

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }

weak_register_no_lock调用这个函数,

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    if (referent->isTaggedPointerOrNil()) return referent_id;

    // ensure that the referenced object is viable
    if (deallocatingOptions == ReturnNilIfDeallocating ||
        deallocatingOptions == CrashIfDeallocating) {
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            // Use lookUpImpOrForward so we can avoid the assert in
            // class_getInstanceMethod, since we intentionally make this
            // callout with the lock held.
            auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
            lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
                                       referent->getIsa());
            if ((IMP)allowsWeakReference == _objc_msgForward) {
                return nil;
            }
            deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
        }

        if (deallocating) {
            if (deallocatingOptions == 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);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

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

    return referent_id;
}

在这里

   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);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

在这里添加,在weak_entry_for_referent如果找到referent(修饰的对象)存储,直接调用append_referrer往里面添加。
如果没有找到,weak_entry_t new_entry(referent, referrer);(对象的指针地址,对象)创建新的实体。
weak_grow_maybe扩容表的处理。
weak_entry_insert把实体添加到弱引用表中。

刚才我们有介绍了referent存放的对象的指针地址,那么实际是什么?我们来调试下,我们先看张图

6

这时可以看出referent就是objc,所以referent就是我们的对象
但是referrer地址不同,我们来分析下weak_entry_t结构。

struct weak_entry_t {
    DisguisedPtr referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

referent存了很多referrer。
如果是weak修饰的属性, referent还是我们的对象,referrers存放我们的属性。

weak大致流程

  • weak是存在弱引用表中(散列表sideTable)
  • 通过sideTable查找到weakTable弱引用表
  • 创建一个weak_entry_t
  • 把referent(对象的指针地址,如weakSelf)加入到weak_entry_t的数组inline_referrers
  • 把weak_table扩容一下
  • 把new_entry加入到weak_table中
    函数流程
  • objc_initWeak
  • storeWeak
  • weak_unregister_no_lock
  • weak_register_no_lock ----->weak_entry_for_referent ----->weak_entry_for_referent -----> new_referrers[i]=entry->inline_referrer[i]
  • weak_entry_t->inline_referrer[i]
  • weak_grow_maybe
  • weak_entry_insert

4 关于弱引用的引用计数

 NSObject *objc = [[NSObject alloc] init];
        NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);
        __weak typeof (id) weakObjc = objc;
        NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);
        NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),objc);

我们看下执行结果,如图


7

这里

   NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);

输出为1
这里

 NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);

输出也是1
这里

   NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),objc);

输出为2。
这是为什么?

   NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),objc);

这里访问的是weakObjc
objc指向这块内存,reatinCount=1,但是weakObjc是弱引用,不持有这个对象,为什么会是2呢,因为弱引用表把我们的这个对象复制了一份,加入引用计数表中,copy了这份对象跟复制的那份是指向同一块内存,我们获取weakObjc的引用计数,是获取copy那份的引用计数
我们断点,运行项目,如图

8

调用objc_loadWeak函数,接着,我们在源码搜下这个函数,如下

id
objc_loadWeak(id *location)
{
    if (!*location) return nil;
    return objc_autorelease(objc_loadWeakRetained(location));
}

这里调用了objc_loadWeakRetained这个函数,我们搜下objc_loadWeakRetained这个函数

id
objc_loadWeakRetained(id *location)
{
    id obj;
    id result;
    Class cls;

    SideTable *table;
    
 retry:
    // fixme std::atomic this load
    obj = *location;
    if (obj->isTaggedPointerOrNil()) return obj;
    
    table = &SideTables()[obj];
    
    table->lock();
    if (*location != obj) {
        table->unlock();
        goto retry;
    }
    
    result = obj;

    cls = obj->ISA();
    if (! cls->hasCustomRR()) {
        // Fast case. We know +initialize is complete because
        // default-RR can never be set before then.
        ASSERT(cls->isInitialized());
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    }
    else {
        // Slow case. We must check for +initialize and call it outside
        // the lock if necessary in order to avoid deadlocks.
        // Use lookUpImpOrForward so we can avoid the assert in
        // class_getInstanceMethod, since we intentionally make this
        // callout with the lock held.
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls);
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            }
            else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
                result = nil;
            }
        }
        else {
            table->unlock();
            class_initialize(cls, obj);
            goto retry;
        }
    }
        
    table->unlock();
    return result;
}

我们在这个函数,再断点进去,看下
如图

9

这里的location就是weakSelf,这里就是获取weakSelf所指向的内存空间。
经过调试发现,是在

 if (! obj->rootTryRetain()) {
            result = nil;
        }

函数调用rootRetain这个函数,这里会执+1操作,如图

10

这里就有一个疑问了,如果一直执行rootRetain会一直+1操作,但是发现不是,weakObjc的引用计数一直2 ,为什么?
因为id obj;是临时变量,出了函数作用域就不存在了,因为会发送release消息。

总结

本篇文章, 我们分析散列表,计用计数,弱引用计数。本篇文章有遗漏的地方,烦请批评指正。

你可能感兴趣的:(iOS-内存管理分析(中))