[iOS] weak实现流程

1. weak关键字

weak关键字修饰的对象指针是弱引用,被引用对象的引用计数不会+1,并在引用对象被释放的时候自动被设置为nil。通常用于解决循环引用问题,如代理以及及block的使用中,相对会较多的使用到weak

为了能够更深入的理解 weak的底层实现,应对面试问到关于weak实现的问题,这次记录并学习一下 weak 实现的源码。

2. 实现的大概流程

我们从下面这段代码开始,来学习一下weak 实现的源码:

{
    NSObject *obj = [[NSObject alloc] init];
    id __weak obj1 = obj;
}

创建weak引用的时候,会走到runtimeobjc_initWeak的这个方法里,可以通过符号断点进行验证,源码如下:

// location 是指向对象的弱指针的地址
// newObj 是对象指针
id objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

从上面可以看到会走到storeWeak方法中,源码如下:

/*
*location : weak指针的地址
newObj : 被指向的对象
*/
template <HaveOld haveOld, HaveNew haveNew,
          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;

 retry:
    if (haveOld) {
        // 如果weak 指针之前指向过其他对象,取出这个旧对象
        oldObj = *location;
        // 以旧对象为 key,从全局 SideTables()中取出旧对象对应的 SideTable
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        // 如果有新值,那么就取出新值对应的 SideTable
        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 非空,防止弱引用机制和+initialize发生死锁
    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);
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // 如果之前weak 指针指向了别的对象,这里清空
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    
    if (haveNew) {
        // 通过 newObj 和 location 生成一个新的 weak_entry_t并插入到 newObj 的弱引用数组中
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table
        if (newObj  &&  !newObj->isTaggedPointer()) {
            // 设置被引用对象 isa 的弱引用标志位为 YES
            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);

    return (id)newObj;
}

从上面这个方法中我们可以大概了解了围绕 weak 指针进行的一些操作:

  • 从全局SideTables()中获取对象所在的 SideTable
  • isa 的非空校验,如果isa没有被初始化,则执行class_initialize(cls, (id)newObj)方法
  • 如果weak 指针之前指向了别的对象,就解除对旧对象的引用
  • 注册新对象的弱引用
  • 设置新对象的弱引用标志符为 YES

大概的流程图如下:

1513131184289942.png

3. 散列表的相关结构

上面storeWeak方法中,我们看到有一个全局的 SideTables()散列表,我们以此为切入点,去查看一下关于weak实现涉及到的整体结构。

3.1 SideTables()

首先,SideTables()是一个静态函数,代码如下:

static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;

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

函数体里面调用了一个全局的静态变量SideTablesMapget()方法,这个静态变量保存了所有的SideTable,是objc命名空间下的一个ExplicitInit类,它里面实现了get()方法,如下:

Type &get() {
        return *reinterpret_cast<Type *>(_storage);
}

这个get()方法其实返回的就是StripedMap类的实例。

3.2 StripedMap

StripedMap 是一个以void *p为 key,PaddedT为 value 的表,实现如下:

template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 }; // 真机下StripedMap存储的 SideTable 数量为 8
#else
    enum { StripeCount = 64 }; // 模拟器下为 64
#endif

    // 对于SideTable的封装
    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    // 存储 PaddedT 的散列表
    PaddedT array[StripeCount];

    // 散列函数,通过对象地址计算出对应 PaddedT在数组中的下标
    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
    // 取值操作,重写了[ ]方法,上面提到的&SideTables()[oldObj]会调用到这个方法
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast<StripedMap<T>>(this)[p]; 
    }

   // ... 省略一些方法
};

综上可以得出 SideTables 的结构实际上如下图所示:

截屏2021-04-06 下午10.18.01.png

3.3 SideTable

上面介绍StripedMap时,其内部有一个哈希表,表中存储的是 PaddedT 结构体,结构体的 value 就是 SideTable,其实现如下:

struct SideTable {
    spinlock_t slock; // 保证原子操作的自旋锁
    RefcountMap refcnts; // 引用计数的 hash 表
    weak_table_t weak_table;  // weak 引用全局 hash 表
    // 构造函数
    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }
    // 析构函数
    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }
};

我们现在把重点放在weak_table_t weak_table上。
SideTable结构图如下:

截屏2021-04-06 下午10.27.27.png

3.4 weak_table_t

上面我们可以看到SideTable 结构体中有一个weak_table_t结构体类型的成员变量,实现如下:

// 全局弱引用表
struct weak_table_t {
    weak_entry_t *weak_entries; // hash 数组,用来存储弱引用对象相关信息的 weak_entry_t
    size_t    num_entries; // hash数组中元素的个数
    uintptr_t mask; // hash 数组的长度(并不是实际的存储个数)-1,主要用来参与哈希函数
    uintptr_t max_hash_displacement; // 最大哈希偏移值
};

结构图如下:


截屏2021-04-06 下午10.39.51.png
3.5 weak_entry_t

上面的weak_table_t中可以看到其内部有一个weak_entry_t的结构体数组,这就是其内部维护的哈希表,我们现在来看下weak_entry_t结构体的实现,如下:

struct weak_entry_t {
    DisguisedPtr<objc_object> referent; // 弱引用的对象
    union {
        // 弱引用数量大于 4 个用到的结构体
        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;
        };
        // 弱引用数量不大于 4 个用到的结构体
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];  // 存储指向该对象的弱引用数组
        };
    };

    // 判断是否用的是 referrers 来存储弱引用指针
    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;
        }
    }
};

从上面的代码实现中可以看出,weak_entry_t结构体存放的是某个对象的所有弱引用指针,存放所有弱引用指针使用的是一个联合体,如果弱引用指针的数量不超过 4 个就用inline_referrers存储,否则用referrers存储。

结构图如下:


截屏2021-04-06 下午10.43.50.png
3.6 weak_referrer_t

weak_entry_t用于存放所有指向某个对象的 weak 指针地址,类型是weak_entry_t,实现如下:

typedef DisguisedPtr<objc_object *> weak_referrer_t;
3.7 结构图总结
截屏2021-04-06 下午10.50.31.png

4. 代码结合结构图具体分析

我们还从👆上面提到的storeWeak方法中进行具体分析,代码如下,标注的一些点在下面会进行具体分析:

template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating> 
storeWeak(id *location, objc_object *newObj)

{
    //校验旧对象和新对象必须存其一
    ASSERT(haveOld  ||  haveNew);
    //校验如果haveNew=true,newObj不能为nil
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

 retry:
    if (haveOld) {
        //如果weak 指针指向旧值,就取出旧值
        oldObj = *location;
        //以旧对象地址为 key取出旧的SideTable
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
     // 取出对应新的SideTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    
    //上锁
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    //校验,如果旧值对不上 goto retry
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    //保证弱引用对象的isa非空,防止弱引用机制和+initialize 发生死锁
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            //如果class没有初始化发送+initialized消息
            class_initialize(cls, (id)newObj);

            previouslyInitializedClass = cls;
            //到这里class肯定已经初始化了,在走一遍
            goto retry;
        }
    }

    if (haveOld) {
    //<<1>>如果weak 指针之前指向了其他对象,在这里清空
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    if (haveNew) {
    //通过newObj和location生成一个新的weak_entry_t并插入到newObj的弱引用数组中(weak_entries)
    //<<2>>
        newObj = (objc_object *)
        weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
        crashIfDeallocating);

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
           //<<3>>  设置 isa 的标志位
           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);

    return (id)newObj;
}
4.1 解除 weak 指针之前旧的指向

方法调用如下:

// 参数 1:weak 指针指向对象所在的全局哈希表
// 参数 2:weak 指针指向的旧对象
// 参数 3:weak 指针的地址
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);

weak_unregister_no_lock方法主要是清除存储在weak_entry_t 中的weak_refrerrer_t,如果删除后weak_entry_t中的数组为空,则将整个weak_entry_tweak_table_t中移除, 方法源码如下:

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_ptr的地址

    weak_entry_t *entry;

    if (!referent) return;
    //<<1>> 查找referent对应的weak_entry_t
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //<<2>>如果entry存在,删除entry中的referrer
        remove_referrer(entry, referrer);
        bool empty = true;
        // 判断entry的动态数组referrers中是否有值
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            //判断entry的定长数组inline_referrers中是否有值
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }
        //如果都是空的将entry从weak_table移除
        if (empty) {
            //<<3>>移除entry
            weak_entry_remove(weak_table, entry);
        }
    }
4.1.1 查找referent对应的weak_entry_t

具体方法实现如下:

// weak_table 当前对象对应的SideTable中的弱引用表
// referent 当前对象
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    ASSERT(referent);
    // 获取所有的weak_entry_t数组
    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;
    
    // 通过对对象指针的哈希方法生成的值与 weak_table->mask 进行 BITMASK 操作得到一个起始值
    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}
  • 每次遍历如果没有在weak_entries中找到referent对应的weak_entry_t,就对index + 1 再进行BITMASK 操作,遍历一次就记录一次,直到大于max_hash_displacement 最大偏移值,返回 nil,说明当前对象在weak_tableweak_entries中没有对应的weak_entry_t,也就是说没有弱引用
  • 如果某个weak_entry_treferent和参数referent相等,说明找到了,返回这个weak_entry_t
4.1.2 删除 entry 中的 referrer

在上一步中我们找到了存储当前对象弱引用的weak_entry_t,现在我们就需要从weak_entry_t中的referrers或者inline_referrers中删除掉之前的弱引用,源码实现如下:

// entry: 当前对象对应的 weak_entry_t
// old_referrer : 弱引用指针的地址
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
  
    if (! entry->out_of_line()) {
      // 从 entry 的定长(长度为 4)数组inline_referrers中查找 old_referrer,如果有则置为 nil
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == old_referrer) {
                entry->inline_referrers[i] = nil;
                return;
            }
        }
        _objc_inform("Attempted to unregister unknown __weak variable "
                     "at %p. This is probably incorrect use of "
                     "objc_storeWeak() and objc_loadWeak(). "
                     "Break on objc_weak_error to debug.\n", 
                     old_referrer);
        objc_weak_error();
        return;
    }

    // 从动态数组中查找
    size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (entry->referrers[index] != old_referrer) {
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
        hash_displacement++;
        if (hash_displacement > entry->max_hash_displacement) {
            _objc_inform("Attempted to unregister unknown __weak variable "
                         "at %p. This is probably incorrect use of "
                         "objc_storeWeak() and objc_loadWeak(). "
                         "Break on objc_weak_error to debug.\n", 
                         old_referrer);
            objc_weak_error();
            return;
        }
    }
    // 置为 nil,并且对应的数量-1
    entry->referrers[index] = nil;
    entry->num_refs--;
}
  • 如果使用的是定长数组(数量为 4 个),那么就对inline_referrers进行遍历查找,如果存在弱引用指针old_referrer,则设为 nil
  • 如果使用的是动态数组,那么就对referrers进行查找,如果存在,设为nil,并将引用数量-1,查找方法和上面👆那一步类似
4.1.3 如果 entry中的数组为空,则从 weak_table_t中删除 entry

源码实现如下:

static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // 如果使用的是动态数组,则释放动态数组的内存
    if (entry->out_of_line()) free(entry->referrers);
    // 以 entry 为起始地址的前sizeof(*entry)个字节区域清零
    bzero(entry, sizeof(*entry));
    // 全局 weak_table_t中的弱引用对象数量-1
    weak_table->num_entries--;
    // 收缩表大小
    weak_compact_maybe(weak_table);
}
  • weak_compact_maybe(weak_table) 收缩表大小
    实现如下:
static void weak_compact_maybe(weak_table_t *weak_table)
{
    // #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
    size_t old_size = TABLE_SIZE(weak_table);

    // 如果 weak_table 的表大小占用超过 1024 个字节,并且 1/16比弱引用的对象的数量还多就收缩表的大小,使其不超过原来的 1/2
    if (old_size >= 1024  && old_size / 16 >= weak_table->num_entries) {
        weak_resize(weak_table, old_size / 8);
        // leaves new table no more than 1/2 full
    }
}
  • weak_resize源码
static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
    // 获取旧的大小
    size_t old_size = TABLE_SIZE(weak_table);
    
    weak_entry_t *old_entries = weak_table->weak_entries;
    // 使用新的大小创建新的new_entries
    weak_entry_t *new_entries = (weak_entry_t *)
        calloc(new_size, sizeof(weak_entry_t));
    // 将weak_table的成员变量重新赋值
    weak_table->mask = new_size - 1;
    weak_table->weak_entries = new_entries;
    weak_table->max_hash_displacement = 0;
    weak_table->num_entries = 0;  // restored by weak_entry_insert below
    
    // 如果old_entries还有值,则进行遍历重新放入 weak_table 新的weak_entries中
    if (old_entries) {
        weak_entry_t *entry;
        weak_entry_t *end = old_entries + old_size;
        for (entry = old_entries; entry < end; entry++) {
            if (entry->referent) {
                // 插入这一步在下面会讲到
                weak_entry_insert(weak_table, entry);
            }
        }
        // 释放旧的
        free(old_entries);
    }
}
4.2 生成新的 weak_entry_t 插入到weak_table_t中的 weak_entryies中

之前storeWeak中相关的代码如下:

...
newObj = (objc_object *)
        weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
        crashIfDeallocating);
...

我们具体来看下weak_register_no_lock方法的源码,实现如下:

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    
    // 如果为 nil,或者是TaggedPointer,则直接 return referent_id
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // 判断当前对象是否正在释放或是否支持弱引用
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           @selector(allowsWeakReference));
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
    }

    if (deallocating) {
        if (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;
        }
    }

  
    weak_entry_t *entry;
   // 判断如果对象已经在 weak_table 中存在弱引用记录,就在原来的 entry 上追加
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    // 如果不存在则创建一个新的 entry,加入到 weak_table中
    else {
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }
    return referent_id;
}
4.2.1 weak_entry_t 中添加新的 weak_referrer_t

append_referrer具体实现如下:

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    // 如果使用的是定长数组,找到为 nil 的位置,赋值即可
    if (! entry->out_of_line()) {
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

       // 如果放不下了,就申请创建动态数组new_referrers
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // 然后将之前定长数组的弱引用赋值给new_referrers
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        // 设置 entry 相关的变量的值
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }

    ASSERT(entry->out_of_line());

    // 如果引用数量超过表大小的 3/4就自动扩容
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    // 在 referrers 中找到一个值为 nil 的 weak_referrer_t,用新的弱引用对其赋值,并将引用数量+1
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
    }
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}
  • grow_refs_and_insert 对某个对象的弱引用表扩容并进行插入
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry, 
                                 objc_object **new_referrer)
{
    ASSERT(entry->out_of_line());
    
    // 获取旧表的大小
    size_t old_size = TABLE_SIZE(entry);
    // 如果之前有旧表,则扩容为之前的 2 倍,否则为 8
    size_t new_size = old_size ? old_size * 2 : 8;
    
    // 获取当前对象所有弱引用指针的数量
    size_t num_refs = entry->num_refs;
    // 获取旧的数组
    weak_referrer_t *old_refs = entry->referrers;
    // mask 赋值
    entry->mask = new_size - 1;
    // 使用新的 size 创建referrers,并给 entry 赋值
    entry->referrers = (weak_referrer_t *)
        calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
    // 刚开始都为 0
    entry->num_refs = 0;
    entry->max_hash_displacement = 0;
    
    // 将老的弱引用指针地址放到新的里边
    for (size_t i = 0; i < old_size && num_refs > 0; i++) {
        if (old_refs[i] != nil) {
            // 进行插入
            append_referrer(entry, old_refs[i]);
            num_refs--;
        }
    }
    // 最后将新的弱引用指针地址进行插入
    append_referrer(entry, new_referrer);
    if (old_refs) free(old_refs);
}
4.2.2 new_entry(referent, referrer) 创建新的 entry

构造方法实现如下:

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

从上面代码中可以看出,刚开始对象没有弱引用指针,使用的是定长数组inline_referrers

4.2.3 weak_grow_maybe weak_table扩容

实现代码如下:

static void weak_grow_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // 如果超过 3/4 则进行扩容,如果之前有,则为原来的 2 倍,否则为 64
    if (weak_table->num_entries >= old_size * 3 / 4) {
        weak_resize(weak_table, old_size ? old_size*2 : 64);
    }
}

这里也是调用weak_resize方法,将weak_table_t进行扩容,上面👆是进行收缩。

4.2.4 weak_table_t 中插入 weak_entry_t

实现代码如下:

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
    weak_entry_t *weak_entries = weak_table->weak_entries;
    ASSERT(weak_entries != nil);
    // 计算出要插入的位置
    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_entries[index].referent != nil) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_entries);
        hash_displacement++;
    }
    // 进行赋值,数量自增
    weak_entries[index] = *new_entry;
    weak_table->num_entries++;
    // 对最大max_hash_displacement偏移值进行赋值,这也是查找时遍历的临界点
    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}

4.3 设置弱引用的标志位

这一步就是标记这个对象有弱引用,在这个对象 dealloc的时候,将所有的弱引用释放掉,实现代码如下:

inline void
objc_object::setWeaklyReferenced_nolock()
{
 retry:
    // 获取对象的 isa 指针
    isa_t oldisa = LoadExclusive(&isa.bits);
    isa_t newisa = oldisa;
    if (slowpath(!newisa.nonpointer)) {
        ClearExclusive(&isa.bits);
        sidetable_setWeaklyReferenced_nolock();
        return;
    }
    // 如果对象isa 的弱引用标志位已经是 true 了,则不进行操作
    if (newisa.weakly_referenced) {
        ClearExclusive(&isa.bits);
        return;
    }
    // 弱引用标志位设为 true
    newisa.weakly_referenced = true;
    //     //如果oldisa.bits和newisa.bits不相等返回NO,继续tery里面的内容,这时候newisa.weakly_referenced已经是true了,所以return

    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}

5. 弱引用指针的释放

- (void)dealloc {
    _objc_rootDealloc(self);
}

⏬

void _objc_rootDealloc(id obj)
{
    ASSERT(obj);

    obj->rootDealloc();
}

⏬

inline void objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

⏬

id object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

⏬

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

⏬

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

⏬

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

终于来到了关键的方法weak_clear_no_lock,实现如下:

 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;
    // 获取当前对象所在的weak_entry_t
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    // 遍历entry的 referrers 数组,将弱引用指针全置为 nil 
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    // 从 weak_table中移除当前对象对应的 entry
    weak_entry_remove(weak_table, entry);
}

上面流程主要是针对于weak实现的介绍,其中对于 isa.nonpointer 还有 isa的标志位以及 Tagged Pointer没有进行过多介绍,之后文章会详细进行介绍。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容