03.SideTable和weak底层实现

我们先看一个小题目,看看下面的输出回事什么样的?

NSObject *objc = [[NSObject alloc] init];

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

__weak typeof(id) weakObjc = objc;

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

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

输出:

1、1、2。是不是很迷惑?别急我们慢慢来

前言

前面在对内存管理进行研究的时候接触到了 SideTalbe ,今天我们也依然会和他见面,它究竟是什么呢?

void 
objc_object::sidetable_lock()
{
    SideTable& table = SideTables()[this];
    table.lock();
}

SideTable 可以看出, SideTable 是有多张的。它是一个 HashMap

一、 SideTable 的结构

struct SideTable {
    spinlock_t slock;
    // 引用计数 Map
    RefcountMap refcnts;
    // 若引用 Map
    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(); }

    ...
};

简单了解了 SideTable 后我们继续回到题目。

二、 weak 做了什么

2.1 调试

在这里打上汇编断点

走到了 objc_initWeak

objc_initWeak

storeWeak

weak_register_no_lock

注册一个 对象 - weak指针 对。如果没有的话,创建一个 weak 的 entry 对象。

参数:

  • weak_table : SideTable 地址

  • referent : 被弱引用的对象

  • referrer : 弱引用指针地址

append_referrer

把 referrer 加进 entry 的 inline_referrers

这里又见到了熟悉的 3/4 扩容

weak_entry_t

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            // 一个对象可以有多个 referent
            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];
        };
    };

    ...

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

三、 回到题目继续分析

我们在这里下个断点,并打开汇编断点:

来到了 objc_loadWeakRetained

objc_loadWeakRetained

内部继续跟进来到了 rootTryRetain ,这里内部引用计数会加

总结

  • weak 原理

    • 得到 SideTable 的弱引用表

    • 得到一个 weak_entry_t

    • referent 加入到 entry 的数组 inline_referrers

    • 如果 weak_table 需要扩容

      • 3/4 扩容,类似与方法列表

    • new_entry 加入到 weak_table

  • 当我们使用一个 weakObjc 的时候,会调用 rootTryRetain ,所以引用计数会 +1。也就是这里我们看到输出为 2 的原因所在。

Last updated