iOS底层原理探索 -- 内存管理之弱引用表

2022-01-14 14:46:07 浏览数 (1)

前言

接着上一篇,我们继续来到OC内存管理系列,关于弱引用表的流程。

从 sidetable_retain 开始 今天的源码阅读

代码语言:javascript复制
idobjc_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

代码语言:javascript复制
struct SideTable {
    spinlock_t slock;
    // 引用计数表
    RefcountMap refcnts; 
    // 弱引用表 ( __weak )
    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<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

(滑动显示更多)

系统会维护多张 SideTable ,( 如果只有一张表的话,所有的对象,使用中会十分的耗费性能(查询,锁操作) ),多张表就会将对象分开存储,随着使用,可以对释放的对象进行表的存储的优化。空间换时间的常规操作。

弱引用表

从测试打印 看起来不太科学开始:

汇编搞一下

其会来到 objc_initWeak ,这是为什么呢?因为在 llvm 符号绑定了。和之前的 super 一样,会找到特定的标识符。

objc_initWeak

代码语言:javascript复制
/** 
 * 初始化指向某个对象位置的新弱指针。
 * 它将用于以下代码 :  
 *
 * (The nil case) 
 * __weak id weakPtr;
 * (The non-nil case) 
 * NSObject *o = ...;
 * __weak id weakPtr = o;
 * 
 * 这个函数对于并发来说不是线程安全的
 * 对弱变量的修改。(并发弱清除是安全的。)
 *
 * @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);
}

(滑动显示更多)

storeWeak

代码语言:javascript复制
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;

    // 为旧值和新值获取锁。
    // 按锁地址订购,防止锁顺序问题。
    // 如果我们下面的旧值发生了变化,请重试。
 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;
    }

    // 防止弱引用机制之间的死锁
    // 和类初始化机制,确保没有
    // 弱引用对象具有未初始化的isa
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(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 (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // 如果有新的值,则分配新的值
    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);

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

(滑动显示更多)

代码语言:javascript复制
haveOld = nil; oldTable = nil;
haveNew = YES; newTable = newTable = &SideTables()[newObj];

(滑动显示更多)

最后 return 出去;

weak流程总结

  • 1:首先我们知道有一个非常牛逼的家伙-sideTable
  • 2:得到sideTable的weakTable 弱引用表
  • 3:创建一个weak_entry_t
  • 4:把referent加入到weak_entry_t的数组inline_referrers
  • 5:把weak_table扩容一下
  • 6:把new_entry加入到weak_table中

回到开头 __weak 那里为什么会打印 2 呢?

我们断点打上, 汇编看一下:

可以看到是调用了 objc_loadWeakRetained 加下来来到

objc_loadWeakRetained

在这里做了一次 obj->rootTryRetain() 的操作,也就是会走我们之前探索的 retain 流程;

苹果为什么会这么操作呢?

再看:

也就是说 weakObjc 和 objc 两者是相互独立的,在 中间的代码块之后, weakObjc 指向了nil;和objc之间是没有关系的;weakObjc 只是加载弱引用表中。

最后会崩,因为找不到了弱引用表(objc 为 nil,怎么能找到呢?)。

0 人点赞