OC内存管理-ARC&MRC、散列表

  • ARCLLVMRuntime配合的结果。
  • ARC中禁止手动调用retain/release/retainCount/dealloc
  • ARC新加了weakstrong属性关键字

一、 retain 源码解析

1.1 rootRetain 核心源码

ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    //TaggedPointer 直接返回
    if (slowpath(isTaggedPointer())) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    oldisa = LoadExclusive(&isa.bits);
......
    do {
        transcribeToSideTable = false;
        newisa = oldisa;
        //纯指针
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            //散列表引用计数 + 1
            else return sidetable_retain(sideTableLocked);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        //非纯指针 nonpointer
        //正在释放(为了多线程)不做处理。
        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 + 1, RC_ONE 从 extra_rc 的最低位开始+1。相当于extra_rc + 1。加满了标记 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;
            //extra_rc 减半
            newisa.extra_rc = RC_HALF;
            //标记有散列表
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
    //isa extre_rc满了
    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 {
......
    }

    return (id)this;
}
  • 判断是否TaggedPointerTaggedPointer直接返回。
  • nonpointer散列表引用计数+1
  • 对象正在释放不进行操作。
  • nonpointerextra_rc + 1
    • extra_rc超载的情况下has_sidetable_rc设置为true
    • extra_rc减半。
    • 散列表加一半extra_rc

1.2 sidetable_retain

id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    //获取对象对应的散列表 SideTable
    SideTable& table = SideTables()[this];
    
    if (!locked) table.lock();
    size_t& refcntStorage = table.refcnts[this];
    //引用计数+1
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        //1 << 2, +2  是在 refcntStorage 上 +2,因为引用计数存储在 SideTable 的 refcntStorage 位置从1开始,不是从0开始。
        //这里+2 相当于+ 0b010,只对1号位置加
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}
  • 获取对象对应的SideTable
  • 获取SideTable中的引用计数表refcnts
  • 从引用计数表中找到refcntStorage
  • refcntStorage +2引用计数+1,这里+2是因为加到对应的位上,从1开始。

1.3 sidetable_addExtraRC_nolock

bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    ASSERT(isa.nonpointer);
    SideTable& table = SideTables()[this];
    //从SideTable对应的引用计数表中取出对象的引用计数refcntStorage
    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;
    //散列表引用计数 + extra_rc的一半。从1号位置开始存,所以需要 delta_rc << 2
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    if (carry) {
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        refcntStorage = newRefcnt;
        return false;
    }
}
  • 获取对象对应的SideTable
  • 获取SideTable中的引用计数表refcnts
  • 从引用计数表中找到refcntStorage
  • refcntStorage + delta_rc << SIDE_TABLE_RC_SHIF引用计数+ extra_rc最大值的一半。

散列表和extra_rc各存储一半是因为extra_rc可以通过isa直接拿到而散列表需要去查找表然后找到对象的引用计数区域。散列表还有加解锁。在extra_rc中操作方便快速。extra_rc每次存储挪一半是为了避免在retainrelease频繁操作的时候而导致散列表频繁操作。

1.4 retain 流程

retain流程
  • TaggedpPointer直接返回。
  • nonpointer isa引用计数表引用计数+1
  • nonpointer isa extra_rc +1
    • 如果extra_rc上溢出(iOS真机255),extra_rc值减半(128)。
    • extra_rc减少一半的值存入引用计数表。

二、 release 源码解析

2.1 release 核心源码

ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
    //判断是否TaggedPointer,TaggedPointer直接返回
    if (slowpath(isTaggedPointer())) return false;

    bool sideTableLocked = false;

    isa_t newisa, oldisa;
......
retry:
    do {
        newisa = oldisa;
        //非 nonpointer
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            //散列表 引用计数-1
            return sidetable_release(sideTableLocked, performDealloc);
        }
        //在释放 返回
        if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            return false;
        }

        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        //extra_rc - 1。减一后如果extra_rc=0了,则标记carry。
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {//跳转underflow
            // don't ClearExclusive()
//            printf("newisa.extra_rc: %d\n",newisa.extra_rc);
            //存储满的情况下
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));

    if (slowpath(newisa.isDeallocating()))
        goto deallocate;

    if (variant == RRVariant::Full) {
        if (slowpath(sideTableLocked)) sidetable_unlock();
    } else {
        ASSERT(!sideTableLocked);
    }
    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 (variant != RRVariant::Full) {
            //清空extra_rc
            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.
            oldisa = LoadExclusive(&isa.bits);
            goto retry;
        }

        // Try to remove some retain counts from the side table.
        //将sidetable中取一半(extra_rc 最大值的一半)存到 extra_rc 中。borrow为借过来的值。
        auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
        //散列表中没有值则标记清空散列表
        bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there

        if (borrow.borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            bool didTransitionToDeallocating = false;
            //extra_rc 值为 borrowed - 1。extra_rc 发生溢出了所以-1存储
            newisa.extra_rc = borrow.borrowed - 1;  // redo the original decrement too
//            printf("newisa.extra_rc111 : %d\n",newisa.extra_rc);
            newisa.has_sidetable_rc = !emptySideTable;
            //存储
            bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
            //存储失败则重新存
            if (!stored && oldisa.nonpointer) {
                // 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.
                uintptr_t overflow;
                //借过来的存入到bits。
                newisa.bits =
                    addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
//                printf("newisa.extra_rc222 : %d\n",newisa.extra_rc);
                newisa.has_sidetable_rc = !emptySideTable;
                if (!overflow) {
                    stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
                    if (stored) {
                        didTransitionToDeallocating = newisa.isDeallocating();
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                ClearExclusive(&isa.bits);
                //没有存储成功则 sidetable 加回去
                sidetable_addExtraRC_nolock(borrow.borrowed);
                oldisa = LoadExclusive(&isa.bits);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            //清空sidetable
            if (emptySideTable)
                sidetable_clearExtraRC_nolock();

            if (!didTransitionToDeallocating) {
                if (slowpath(sideTableLocked)) sidetable_unlock();
                return false;
            }
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

    //没有散列表直接释放
deallocate:
    // Really deallocate.

    ASSERT(newisa.isDeallocating());
    ASSERT(isa.isDeallocating());

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    //调用dealloc
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}
  • 判断是否TaggedPointerTaggedPointer直接返回。
  • nonpointer isa则直接散列表引用计数-1
  • 如果对象在释放直接返回false
  • nonpointer isaextra_rc - 1
  • extra_rc溢出的情况判断has_sidetable_rc
  • has_sidetable_rctrue的情况则sidetable减去extra_rc最大值的一半,值存储到borrow
  • extra_rc设置为borrow.borrowed - 1(溢出了要减去1再存储,相当于这次的release)。
  • borrow.remaining == 0的情况则设置emptySideTable清空对象对应的SideTable
  • extra_rc0的情况则调用发送dealloc消息。

2.2 sidetable_release

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

    bool do_dealloc = false;

    if (!locked) table.lock();
    //获取table中对象对象的引用计数 refcnts
    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)) {
        //引用计数-1
        refcnt -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return do_dealloc;
}
  • 通过对象获取SideTablerefcnts
  • refcnts - 2相当于引用计数-1
  • 如果引用计数为0则发送dealloc消息。

2.3 sidetable_subExtraRC_nolock

objc_object::SidetableBorrow
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
    ASSERT(isa.nonpointer);
    SideTable& table = SideTables()[this];

    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()  ||  it->second == 0) {
        // Side table retain count is zero. Can't borrow.
        return { 0, 0 };
    }
    size_t oldRefcnt = it->second;
    // isa-side bits should not be set here
    ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    //sidetable 减少 extra_rc 的最大值的一半(这里有位运算平移)
    size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
    ASSERT(oldRefcnt > newRefcnt);  // shouldn't underflow
    it->second = newRefcnt;
    return { delta_rc, newRefcnt >> SIDE_TABLE_RC_SHIFT };
}
  • 通过对象获取SideTablerefcnts
  • newRefcntoldRefcnt减去extra_rc一半。
  • 返回delta_rc以及剩余的newRefcnt

2.4 release流程

release流程
  • TaggedpPointer直接返回false
  • nonpointer isa引用计数表引用计数-1。如果引用计数为0则调用dealloc返回true,否则返回false
  • nonpointer isa extra_rc -1
    • 如果extra_rc下溢出 ,判断has_sidetable_rc
      • 没有引用计数表则调用dealloc,返回true
      • 有引用计数表则减去extra_rc最大值的一半(128)存入extra_rc散列表中如果没有值了则清空散列表,返回false
    • 如果extra_rc != 0 ,返回false

总结:
retain 针对相应引用计数位+1,开启nonpointer的情况下,如果引用计数出现上溢出,那么开始分开存储,一半存到散列表。
release 针对相应引用计数位-1,开始nonpointer的情况下,如果引用计数出现下溢出,去散列表借来的引用计数 -1 存到extra_rc,依然下溢出则调用dealloc

三、散列表(SideTable)

retainrelease的源码中SideTable是通过SideTables获取的:

SideTable& table = SideTables()[this];

那么证明SideTable是有多张的,SideTables的定义如下:

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

class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif
......
}

iPhone真机下SideTables中有8SideTable,其它则为64张。
SideTable对应的结构:

struct SideTable {
    spinlock_t slock;//os_unfair_lock
    RefcountMap refcnts;//引用计数表
    weak_table_t weak_table;//弱引用表
......
}

散列表中存储了锁、引用计数表、弱引用表。

那么为什么使用多张表呢?
由于SideTable有加锁和解锁,如果在整个系统中如果共用一张表那么就会有性能消耗(互斥)。多张表内存可以置空回收。不是每个对象开辟一张表为了效率和性能。

SideTables结构图下:

image.png

3.1 引用计数(retainCount)

inline uintptr_t 
objc_object::rootRetainCount()
{
    //TaggedPointer 对象指针强转返回。
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
    //nonpointer
    if (bits.nonpointer) {
        //extra_rc,之前的版本为 extra_rc + 1。由于之前的版本 alloc 的时候 extra_rc 不进行 +1。目前版本 alloc 的时候进行了赋值 1。
        uintptr_t rc = bits.extra_rc;
        if (bits.has_sidetable_rc) {
            // extra_rc + 散列表引用计数
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }
    
    sidetable_unlock();
    //非 nonpointer 获取散列表中引用计数。
    return sidetable_retainCount();
}
  • TaggedPointer直接返回对象的地址强转为unsigned long
  • nonpointer返回extra_rc + 引用计数表中引用计数extra_rc这里直接返回没有+1,之前的版本会有+1操作。alloc之前不会对extra_rc赋值为1,现在版本会赋值为1
  • nonpointer直接返回引用计数表中引用计数。

alloc的过程中在进行initIsa的时候对extra_rc进行了赋值:

image.png

SideTable数据内容如下:

(lldb) p table
(SideTable) $5 = {
  slock = {
    mLock = (_os_unfair_lock_opaque = 775)
  }
  refcnts = {
    Buckets = 0x0000000102b04080
    NumEntries = 1
    NumTombstones = 0
    NumBuckets = 4
  }
  weak_table = {
    weak_entries = 0x0000000000000000
    num_entries = 0
    mask = 0
    max_hash_displacement = 0
  }
}

refcnts中存储了引用计数的Buckets,其中存储了DisguisedPtr<objc_object>(包装了引用计数),与关联对象的存储有些类似。

3.2 弱引用

有如下代码:

NSObject *obj = [NSObject alloc];
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj);//1
__weak typeof(id) weakObjc = obj;
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc);//2
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj);//1

运行输出:

1 - <NSObject: 0x101622330>
2 - <NSObject: 0x101622330>
1 - <NSObject: 0x101622330>

按照正常理解weak不增加引用计数,obj输出1没问题。weakObjc的引用计数为什么输出2

3.2.1 弱引用表

要了解weak的引用计数首先要清楚weak表的存储逻辑。通过汇编跟踪发现__weak修饰的变量会进入objc_initWeak

image.png

那么__weak是怎么与objc_initWeak关联起来的呢?
llvm中有相关的映射,weak__weak最终映射到了objc_initWeak

image.png

弱引用的存储与释放:

id
objc_initWeak(id *location, id newObj)
{
    //对象不存在直接返回。
    if (!newObj) {
        //weak 指针置为 nil
        *location = nil;
        return nil;
    }
    //执行存储操作
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

void
objc_destroyWeak(id *location)
{
    //与init同一个函数。传递 newObj 为 nil
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

最终都会调用同一个函数storeWeak

3.2.1.1 storeWeak

//c++ 模板参数
template <HaveOld haveOld, HaveNew haveNew,
          enum 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:
    //整体是SideTables
    if (haveOld) {//是否有旧值,第一次进来没有。
        oldObj = *location;
        //取旧表
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        //取weak指针对应的新表地址
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    //锁定两张表
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
......

    // 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<haveOld, haveNew>(oldTable, newTable);

    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}
  • 获取oldTablenewTablenewTable用来存储,oldTable用来释放。
  • 通过SideTables获取obj对应的SideTable
  • 如果是释放调用weak_unregister_no_lock释放弱引用指针。参数传递SideTableweak_table以及obj和弱引用指针。
  • 如果是存储调用weak_register_no_lock存储弱引用指针。参数传递SideTableweak_table以及obj和弱引用指针。

3.2.1.2 weak_unregister_no_lock

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return;
    //找到对象的 weak_entry_t
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //将弱引用指针从weak_entry_t中移除。
        remove_referrer(entry, referrer);
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }

        if (empty) {
            //如果entry为空了,则将entry从整个weak_table中移除
            weak_entry_remove(weak_table, entry);
        }
    }
}
  • weak_table中找到对象对应的weak_entry_t
  • 调用remove_referrer遍历weak_entry_tinline_referrers或者referrers将对应index位置的值置为nil,并且num_refs - 1
  • 如果weak_entry_t中已经没有值了,则调用weak_entry_removeentryweak_table中清除并且释放空间。

3.2.1.3 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;

  ......

    // now remember it and where it is being stored
    weak_entry_t *entry;
    //根据弱引用对象从weak_table中找出weak_entry_t
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //将弱引用指针加入entry
        append_referrer(entry, referrer);
    } 
    else {
        //通过弱引用指针与对象创建new_entry
        weak_entry_t new_entry(referent, referrer);
        //weak_table扩容
        weak_grow_maybe(weak_table);
        //将new_entry插入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_table中找到对象对应的weak_entry_t
  • 找到对应的weak_entry_t调用append_referrer将弱引用指针加入weak_entry_t(因为释放过程中有置空操作,所以找空位nil加入,这个过程中可能会进行扩容)。
  • 找不到则根据对象和弱引用指针创建weak_entry_t
    • 调用weak_grow_maybe尝试扩容。
    • 调用weak_entry_insert将创建的weak_entry_t加入weak_table

散列表完整结构:


散列表结构

3.2.2 weak 的引用计数

到目前为止仍然解释不了为什么之前的案例弱引用计数为2,修改代码如下:

NSObject *obj = [NSObject alloc];
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//1
__weak typeof(id) weakObjc = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//1

NSObject *obj2 = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj2)),obj2,&obj2);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);//3

__weak typeof(id) weakObjc2 = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);//3
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc2)),weakObjc2,&weakObjc2);//3

输出:

1 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f8
1 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f0
3 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f8
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
3 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f8
3 - <NSObject: 0x101a1a730> - 0x7ffeefbff4e8

obj引用计数为2很好理解,weakObjc多次指向也只增加了一次。weakObjc的引用计数看着是对象的引用计数+weak1次。

CFGetRetainCount调用的是retainCount,那么显然获取的是obj的引用计数,那么多的1肯定做了额外的处理。
有如下代码,对NSLog打断点:

NSObject *obj = [NSObject alloc];
__weak typeof(id) weakObjc = obj;
NSLog(@"%zd",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)));

在调用retainCount之前调用了objc_loadWeakRetained:

image.png

3.2.2.1 objc_loadWeakRetained

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

    SideTable *table;
    
 retry:
    // fixme std::atomic this load
    //通过 weak 指针获取 obj 临时变量。此时引用计数仍然不变。
    obj = *location;
    if (obj->isTaggedPointerOrNil()) return obj;
    
    table = &SideTables()[obj];
    
    table->lock();
    if (*location != obj) {
        table->unlock();
        goto retry;
    }
    //可以尝试 table->unlock() 然后读取 _objc_rootRetainCount(obj)
    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());
        //引用计数 +1,此时 obj 的引用计数变了。
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    }
    else {
        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;
        }
    }
    //这个时候返回的`retainCount`就多了1。
    table->unlock();
    return result;
}
  • 获取弱引用指针对应的obj
  • 调用rootTryRetainobj引用计数+1

验证:


image.png

在调用了objc_loadWeakRetained后调用了retainCount获取obj的引用计数。然后调用objc_release释放这次增加的引用计数。

weak并不会增加引用计数,CFGetRetainCount如果获取的是weak指针的引用计数会先调用objc_loadWeakRetained对对象的引用计数+1,再调用retainCount获取引用计数,然后调用objc_release对对象的引用计数-1

那么为什么weak的引用计数要临时+1呢?
为了在CFGetRetainCount的过程中,weakObjc不被释放。

__weak typeof(id) weakObjc = nil;
{
    NSObject *obj = [NSObject alloc];
    NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);
    weakObjc = obj;
    NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);
    NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);
}
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);

上面的代码虽然会崩溃,仍然可以断点查看weakObjc信息:

image.png

弱引用指针指向的对象已经释放了,弱引用指针还没有释放,出了弱引用指针作用域才释放。这么做的好处是弱引用指针的管理与对象的管理完全分开了。

弱引用表调用流程:

  • 弱引用指针存储在全局SideTables中。
  • 通过对象获取SideTable取到其中的weak_table
  • 创建weak_entry_t,将弱引用指针包装后加入创建的weak_entry_treferrersinline_referrers)中。
  • 判断是否需要扩容weak_table
  • 将创建的weak_entry_t加入weak_table中。
弱引用存储销毁调用逻辑

四、strong & unsafe_unretain

NSObject *obj = [NSObject alloc];
NSObject *obj1 = obj;
HPObject *objc = [HPObject alloc];
objc.objc = obj;

strong修饰的属性或者变量,当属性变量赋值的时候会调用objc_storeStrong(编译时期确定):

image.png

image.png

image.png

objc_storeStrong源码如下:

void
objc_storeStrong(id *location, id obj)
{
    //旧值
    id prev = *location;
    if (obj == prev) {
        return;
    }
    //retain 新值
    objc_retain(obj);
    //赋值新值给指针
    *location = obj;
    //release 旧值
    objc_release(prev);
}

objc_storeStrong过程中会先retain新值然后赋值,最后release旧值。
在源码中会根据变量的修饰符来确定调用的方法:

image.png

如下代码:

@property (nonatomic, strong) NSObject *objc;
@property (nonatomic, weak) NSObject *objc1;
@property (nonatomic, unsafe_unretained) NSObject *objc3;

编译后对应的汇编伪代码:

-(void)setObjc:(void *)arg2 {
    objc_storeStrong(self + 0x40, arg2);
    return;
}

-(void)setObjc1:(void *)arg2 {
    objc_storeWeak(self + 0x48, arg2);
    return;
}

-(void)setObjc3:(void *)arg2 {
    self->_objc3 = arg2;
    return;
}
  • strong修饰的变量底层会调用objc_storeStrong先进行新值的retain然后赋值,最后旧值进行release
  • weak底层调用objc_storeWeakweak指针加入弱引用表中。在dealloc的时候会自动将weak指针置为nil
  • unsafe_unretained直接用新值赋值指针,在dealloc的时候并不会自动置为nil,可能会造成野指针访问

总结:

  • retain 针对相应引用计数位+1,开启nonpointer的情况下,如果引用计数出现上溢出,那么开始分开存储,一半存到散列表。
  • release 针对相应引用计数位-1,开启nonpointer的情况下,如果引用计数出现下溢出,去散列表借来的引用计数 -1 存到extra_rc,依然下溢出则调用dealloc
  • 散列表
    • iPhone真机下SideTables中有8SideTable,其它则为64张。
    • 每张SideTable包含了 引用计数表 和 弱引用表。
  • 引用计数表(RefcountMap)的Buckets存储了对象对应的引用计数的包装。
  • 弱引用表(weak_table_t)的weak_entries存储了对象的弱引用指针weak_entry_tweak_entry_t中存储了指向的对象和指向该对象的弱引用指针集合referrersreferrer中存储了包装的弱引用指针。
  • 引用计数
    • TaggedPointer直接返回对象的地址强转为unsigned long
    • nonpointer返回extra_rc + 引用计数表中引用计数extra_rc这里直接返回没有+1,之前的版本会有+1操作。alloc之前不会对extra_rc赋值为1,现在版本会赋值为1
    • nonpointer直接返回引用计数表中引用计数。
    • weak并不会增加引用计数,CFGetRetainCount会调用objc_loadWeakRetainedweak指向的对象引用计数+1,调用完retainCount后调用objc_release对引用计数-1
  • 弱引用表与对象是分开管理的,各自在作用域处理自身逻辑。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容