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

2,799 阅读5分钟
写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议
  11. iOS 底层原理探索 之 消息转发流程
  12. iOS 底层原理探索 之 应用程序加载原理dyld (上)
  13. iOS 底层原理探索 之 应用程序加载原理dyld (下)
  14. iOS 底层原理探索 之 类的加载
  15. iOS 底层原理探索 之 分类的加载
  16. iOS 底层原理探索 之 关联对象
  17. iOS底层原理探索 之 魔法师KVC
  18. iOS底层原理探索 之 KVO原理|8月更文挑战
  19. iOS底层原理探索 之 重写KVO|8月更文挑战
  20. iOS底层原理探索 之 多线程原理|8月更文挑战
  21. iOS底层原理探索 之 GCD函数和队列
  22. iOS底层原理探索 之 GCD原理(上)
  23. iOS底层 - 关于死锁,你了解多少?
  24. iOS底层 - 单例 销毁 可否 ?
  25. iOS底层 - Dispatch Source
  26. iOS底层 - 一个栅栏函 拦住了 数
  27. iOS底层 - 不见不散 的 信号量
  28. iOS底层 GCD - 一进一出 便成 调度组
  29. iOS底层原理探索 - 锁的基本使用
  30. iOS底层 - @synchronized 流程分析
  31. iOS底层 - 锁的原理探索
  32. iOS底层 - 带你实现一个读写锁
  33. iOS底层 - 谈Objective-C block的实现(上)
  34. iOS底层 - 谈Objective-C block的实现(下)
  35. iOS底层 - Block, 全面解析!
  36. iOS底层 - 启动优化(上)
  37. iOS底层 - 启动优化(下)
  38. iOS底层原理探索 -- 内存管理 之 内存五大区
  39. iOS底层原理探索 -- 内存管理 之 Tagged Pointer Format Changes
  40. iOS底层原理探索 -- 内存管理 之 retain & release

以上内容的总结专栏


细枝末节整理


前言

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

从 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

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

弱引用表

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

image.png

汇编搞一下

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

image.png

objc_initWeak

/** 
 * 初始化指向某个对象位置的新弱指针。 
 * 它将用于以下代码 :  
 *
 * (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

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;
}
haveOld = nil; oldTable = nil;
haveNew = YES; newTable = newTable = &SideTables()[newObj];

截屏2021-09-13 16.24.17.png

haveNew = YES; 
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);

image.png

最后 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 呢?

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

image.png

未命名.jpg

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

objc_loadWeakRetained

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

未命名.jpg

image.png

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

再看:

image.png

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

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