weak 的原理探析

1,070 阅读12分钟

最近同事问了一句说:一个 weak 对象是怎么被释放的?什么时候会被释放?

我想了想说,就是把 SideTablesweak_table 散列表中持有这个对象的 referent 指针置为 nil ,这个 weak 对象就被释放了,具体释放时机是这个对象dealloc 的时候调用了 objc_destructInstance 时候检查散列表清理。

然后想了想,这里还是之前看过的,所以再回顾一下。

weak 的原理

创建类 Objc_Weak_Class,简单实现一下:

//  Objc_Weak_Class.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Objc_Weak_Class : NSObject

- (void)weakMethod;

@end

NS_ASSUME_NONNULL_END


//  Objc_Weak_Class.m

#import "Objc_Weak_Class.h"

@implementation Objc_Weak_Class

- (void)weakMethod
{
    Objc_Weak_Class *o = [[Objc_Weak_Class alloc] init];
    __weak id weakPtr = o;
    
    NSLog(@"weakMethod = %@",o);
}
@end

每次找不到怎么办的时候就汇编呗,万一能看到重要信息呢。(这个很重要,希望给看到这篇文章的读者们提供一种解决问题的思路。)

不知道怎么看汇编的朋友看这里,开启这个。

好,上面的准备工作结束了,我们给这里打个断点。(等程序运行到这里的时候,开启查看汇编)

汇编如下,汇编中出现 call xxx ,代表调用了一个 xxx 方法。

这里我们看到了3个重要方法调用,那就一点点分析吧。(objc_alloc_init 这个就不说明了)

  • objc_alloc_init 初始化一个对象 [[cls alloc] init]
  • objc_initWeak 初始化 weak 的一些东西;
  • objc_destroyWeak 销毁 weak的一些东西。

1、objc_initWeak

command + shift + o 搜索 objc_initWeak(推荐使用这个搜索,有时候在xcode左上角的搜索搜不到),源码如下:

/** 
 * This function IS NOT thread-safe with respect to concurrent 
 * modifications to the weak variable. (Concurrent weak clear is safe.)
 *
 * @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);
}

这里苹果还给了一段代码,告诉我们如何能出发这个方法的调用。

/** 
 * Initialize a fresh weak pointer to some object location. 
 * It would be used for code like: 
 *
 * (The nil case) 
 * __weak id weakPtr;
 * (The non-nil case) 
 * NSObject *o = ...;
 * __weak id weakPtr = o;
 */

看到上方有一行注释:This function IS NOT thread-safe with respect to concurrent modifications to the weak variable. (Concurrent weak clear is safe.),这里意思是说针对 weak 变量的修改不是线程安全的,但是清理 weak 是线程安全的。

为什么线程不安全,看完这些分析,就明白了。(写在了在最下方)

OK,继续进入 storeWeak 方法:

2、storeWeak

因为是 initWeak ,所以认为没有旧值,不影响我们分析,就是断点断不到旧值释放。

  • haveOld = flase
  • haveNew = ture
static id 
storeWeak(id *location, objc_object *newObj)
{
    //检查新旧对象必须存在一个
    ASSERT(haveOld  ||  haveNew);
    
    //如果 haveNew == NO,判断是否 newObj != nil 崩溃
    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:
    if (haveOld) {
        //取出旧值
        oldObj = *location;
        
        //这里的 & 是引用的意思 "[oldObj]" 是对 '[]'的重载
        //其实StripedMap是一个以void *p为key,PaddedT为value的的表。
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        //取出新对象的散列表
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    //散列表加锁
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    //如果有旧值,但是 *location != oldObj 代表可能有其他线程修改了,就从头来
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    
    //通过确保弱引用对象没有一个未初始化的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);

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

    // 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()) {
            //设置 obj 中 isa.weakly_referenced = true; 代表有弱引用
            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.
    
    //必须在没有锁的情况下调用,因为它可以调用任意代码。
    //特别是,即使_setWeaklyReferenced没有实现,resolveInstanceMethod:也可能实现,也可能回调弱引用机制。
    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}

看完上方的代码和注释,梳理一下流程:

  • 1、以旧值和新值为key获取散列表对象
  • 2、获取了散列表之后,判断如果 *location != oldObj ,可能发生线程争夺,重头开始
  • 3、如果有旧值,释放旧值指针
  • 4、向新值散列表中插入新值指针地址
  • 5、设置弱引用 isa.weakly_referenced = ture

3、weak_entry_t 结构体说明

为了更好的理解下方的内容,先对 weak_entry_t 结构体进行说明。

struct weak_entry_t {
    
    //弱引用对象
    DisguisedPtr<objc_object> referent;
    
    //联合体,公用一块内存
    union {
        
        //弱引用数组 weak_referrer_t 大于 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; //最大哈希冲突值
        };
        
        //WEAK_INLINE_COUNT : 4
        //弱引用数组 weak_referrer_t 小于等于 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);
    }

    //memcpy 复制内存指针方法
    //覆盖老数据
    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 存放了某个对象的所有弱引用指针,如果弱引用对象数量不超过四个就报错在结构体数组 inline_referrers,否则保存在 referrers。并且在使用inline_referrers 数组的内存会在 weak_entry_t 初始化的时候一并分配好,而不是需要用到的时候再去申请新的内存空间,从而达到提到运行效率的目的。

4、weak_unregister_no_lock

删除一个已经注册的弱引用。

**
 * 取消注册一个已经注册的弱引用。
 * 当referrer的存储即将消失,但referent还没有死时使用。(否则,稍后将referrer归零将是一个错误的内存访问。)

 * 如果referent/referrer不是当前激活的弱引用,则不做任何操作。
 * 不为0的引用。
 *
 * FIXME目前需要旧的参考值被传入(lame)
 * 如果referrer被收集,FIXME将自动取消注册
 *
 * @param weak_table 全局弱表。
 * @param referent 引用对象。
 * @param referrer 弱引用。
 */


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;

    //开始查找
    
    //找打到了 entry 就处理
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        
        //从弱引用表中删除
        remove_referrer(entry, referrer);
        
        //用于判断是否这个entry内是否是空的
        bool empty = true;
        
        //如果使用了  weak_referrer_t *referrers 且 引用数 num_refs != 0 代表还有对象在用
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            //否则使用了 inline_referrers
            //判断 inline_referrers 有没有值
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }

        if (empty) {
            //移除这个 entry
            weak_entry_remove(weak_table, entry);
        }
    }

    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}

1、weak_entry_for_referent

从上方代码看到了 weak_entry_for_referent 是去查找弱引用表中这个对象的 entry ,接着分析,源码如下:

/** 
 * Return the weak reference table entry for the given referent.  //返回给定引用的弱引用表项。
 * If there is no entry for referent, return NULL. //如果referent没有条目,返回NULL
 * Performs a lookup. //执行查找
 *
 * @param weak_table 
 * @param referent The object. Must not be nil.
 * 
 * @return The table of weak referrers to this object. 
 */
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 和  weak_table->max_hash_displacement 在 append_referrer 方法中复制了,看 append_referrer 即可。
    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    
    //这里是在哈希查找
    
    //直到在全局弱引用表里到了 referent 或者大于最大哈希查找冲突数 就返回
    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];
}

2、remove_referrer

查找到了弱引用对象就要删除上一次保存的值,源码如下:

/** 
 * Remove old_referrer from set of referrers, if it's present.
 * Does not remove duplicates, because duplicates should not exist. 
 * 
 * @todo this is slow if old_referrer is not present. Is this ever the case? 
 *
 * @param entry The entry holding the referrers.
 * @param old_referrer The referrer to remove. 
 */
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    //如果使用的是 inline_referrers 就在这里找,并置为nil
    if (! entry->out_of_line()) {
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == old_referrer) {
                
                //找到后在数组中置为nil
                entry->inline_referrers[i] = nil;
                return;
            }
        }
        
        //因为调用 remove_referrer 表示这个 entry 被找到了,如果发现没有找到就 crash
        _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;
    }

    //如果使用的是 referrers 就来到了这里
    
    //这里和查找 entry 流程一样
    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++;
        
        //因为调用 remove_referrer 表示这个 entry 被找到了,如果发现没有找到就 crash
        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;
        }
    }
    
    //删除这个弱引用对象
    entry->referrers[index] = nil;
    
    //引用数减一
    entry->num_refs--;
}

3、weak_entry_remove

从全局弱引用表中删除这个弱引用对象的数据,清空所占的内存空间。

/**
 * Remove entry from the zone's table of weak references.
 */
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // remove entry
    
    //如果使用referrers 释放 referrers
    if (entry->out_of_line()) free(entry->referrers);
    
    // 以entry为起始地址的前sizeof(*entry)个字节区域清零
    bzero(entry, sizeof(*entry));

    //全局弱引用表,弱引用对象数量-1
    weak_table->num_entries--;

    //收缩表大小,不浪费空间
    weak_compact_maybe(weak_table);
}

4、weak_compact_maybe

判断是否需要收缩表,原始全局散列表 old_size > 1024 并且全局散列表下的弱引用对象的个数 num_entries < old_size /16 才进行收缩。

// Shrink the table if it is mostly empty.
static void weak_compact_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // Shrink if larger than 1024 buckets and at most 1/16 full.
    
    //原始全局散列表 old_size > 1024 并且全局散列表下的弱引用对象的个数 num_entries < old_size /16 才进行收缩
    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
    }
}

5、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;
    
    //创建新的数组空间
    weak_entry_t *new_entries = (weak_entry_t *)
        calloc(new_size, sizeof(weak_entry_t));

    //重新复制原表数据
    weak_table->mask = new_size - 1;
    weak_table->weak_entries = new_entries;
    weak_table->max_hash_displacement = 0;
    
    //会重新 insert,恢复正确数据
    weak_table->num_entries = 0;  // restored by weak_entry_insert below
    
    //循环插入
    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);
    }
}

5、weak_register_no_lock

插入当前弱引用对象

/** 
 * Registers a new (object, weak pointer) pair. Creates a new weak
 * object entry if it does not exist.
 * 注册一个新的object 和 weak pointer的配对
 * 创建一个新的弱对象条目(如果它不存在)
 * 
 * @param weak_table The global weak table.
 * @param referent The object pointed to by the weak reference.
 * @param referrer The weak pointer address.
 */
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;

    //如果是 isTaggedPointe 对象 或者 对象 = nil 直接返回
    if (referent->isTaggedPointerOrNil()) return referent_id;

    // ensure that the referenced object is viable
    if (deallocatingOptions == ReturnNilIfDeallocating ||
        deallocatingOptions == CrashIfDeallocating) {
        bool deallocating;
        
        //判断ISA->hasCustomRR(),
       
       	//此比特位会在该类或父类重写下列方法时
        //retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference返回true
        
        //通常状况咱们都不会重写这些方法,所以会返回false,取反就为true
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            // Use lookUpImpOrForward so we can avoid the assert in
            // class_getInstanceMethod, since we intentionally make this
            // callout with the lock held.
            
            //使用lookupimportforward,
            //这样我们就可以避免class_getInstanceMethod中的assert,
            //因为我们故意在锁被持有的情况下调用这个callout。
            
            auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
            lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
                                       referent->getIsa());
            if ((IMP)allowsWeakReference == _objc_msgForward) {
                return nil;
            }
            deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
        }

        //如果被析构了,就crash,这里不允许当前对象被析构
        if (deallocating) {
            if (deallocatingOptions == 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;
            }
        }
    }

    // now remember it and where it is being stored
    ///现在记住它和它被存储的位置
    weak_entry_t *entry;
    
    //查找弱引用对象
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //查到了就拼接
        append_referrer(entry, referrer);
    } 
    else {
        
        //没有查到
        
        //初始化
        weak_entry_t new_entry(referent, referrer);
        
        //看看是否需要扩容, 4分之3定律
        weak_grow_maybe(weak_table);
        
        //将弱引用插入到哈希表里面
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

1、weak_grow_maybe

如果大于等于 原始大小的4分之3就需要扩容,如果原始表不存在,就生成新表大小为64个字节

// Grow the given zone's table of weak references if it is full.
static void weak_grow_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // Grow if at least 3/4 full.
    if (weak_table->num_entries >= old_size * 3 / 4) {
        
        // 如果 old_size = 0 ,就扩容为 64
        weak_resize(weak_table, old_size ? old_size*2 : 64);
    }
}

2、weak_entry_insert

weak_entry_insert 方法中不判断全局弱引用表中是否存在当前对象直接插入。

这里和我们写代码思想是一样的,为了更好的性能和复用,业务逻辑在上层自己判断,下层只操作即可。

/** 
 * Add new_entry to the object's table of weak references.
 * Does not check whether the referent is already in the table.
 */

/**
 * 在对象的弱引用表中添加new_entry。
 * 不检查referent是否已经在表中。
 */

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;
    
    //全局弱引用表中弱引用对象数 + 1
    weak_table->num_entries++;

    //更新最大哈希冲突数
    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}

3、append_referrer

/** 
 * Add the given referrer to set of weak pointers in this entry.
 * Does not perform duplicate checking (b/c weak pointers are never
 * added to a set twice). 
 *
 * @param entry The entry holding the set of weak pointers. 
 * @param new_referrer The new weak pointer to be added.
 */
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    //如果使用的是 inline_referrers ,找到一个空位就插入
    if (! entry->out_of_line()) {
        // Try to insert inline.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

        // Couldn't insert inline. Allocate out of line.
        
        //如果 inline_referrers 没有空位了,那么就需要更换共用体中结构体的类型
        
        //使用 weak_referrer_t *referrers
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        
        //将 inline_referrers 赋值到 referrers
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        
        //表示 referrers 启用 对应 out_of_lin()方法的调用
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        
        //mask = 3
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }

    ASSERT(entry->out_of_line());

    //判断是否需要扩容
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    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);
    }
    
    //这里是更新当前 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 的弱引用数
    entry->num_refs++;
}

6、callSetWeaklyReferenced

callSetWeaklyReferenced 调用 setWeaklyReferenced 方法设置对象的弱引用标志位为 ture

// Call out to the _setWeaklyReferenced method on obj, if implemented.
static void callSetWeaklyReferenced(id obj) {
    if (!obj)
        return;

    Class cls = obj->getIsa();

    if (slowpath(cls->hasCustomRR() && !object_isClass(obj))) {
        ASSERT(((objc_class *)cls)->isInitializing() || ((objc_class *)cls)->isInitialized());
        void (*setWeaklyReferenced)(id, SEL) = (void(*)(id, SEL))
        class_getMethodImplementation(cls, @selector(_setWeaklyReferenced));
        if ((IMP)setWeaklyReferenced != _objc_msgForward) {
          (*setWeaklyReferenced)(obj, @selector(_setWeaklyReferenced));
        }
    }
}

1、setWeaklyReferenced_nolock

核心代码 newisa.weakly_referenced = true; 设置对象的弱引用标志位为 ture

inline void
objc_object::setWeaklyReferenced_nolock()
{
    isa_t newisa, oldisa = LoadExclusive(&isa.bits);
    do {
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            sidetable_setWeaklyReferenced_nolock();
            return;
        }
        if (newisa.weakly_referenced) {
            ClearExclusive(&isa.bits);
            return;
        }
        
        //这里设置
        newisa.weakly_referenced = true;
    } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
}

7、objc_destroyWeak

objc_destroyWeak 的流程和 objc_initWeak 一样只不过传入新对象为 nil,只清理不插入。

void
objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

8、关于 weak 对象在 dealloc 时候的清理

调用顺序 dealloc -> clearDeallocating -> sidetable_clearDeallocating() -> sidetable_clearDeallocating() -> weak_clear_no_lock()

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 *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
    
    //取联合体中保存的 referrers 和 count
    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;
    }
    
    //清理
    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();
            }
        }
    }
    
    //移除这个 entry
    weak_entry_remove(weak_table, entry);
}

9、关于 weak 线程不安全问题

我们把上述代码精简后,就能明白了:

  • objc_initWeak 初始化 weak 的一些东西;
  • objc_destroyWeak 销毁 weak的一些东西。

objc_initWeak :

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

objc_destroyWeak:

void objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

storeWeak

static id 
storeWeak(id *location, objc_object *newObj)
{
    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

 retry:
    //...
    //这里的代码是取 oldTable 和 newTable

    //散列表加锁
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
	
    //...
    
    ///取消注册一个已经注册的弱引用。
    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    
    //...
    
    //插入新的 location
    newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
                                  
    //...
    
    
    //释放锁
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}

上述代码能看到 weak 的初始化 (objc_initWeak) 和销毁 (objc_destroyWeak)都调用了 storeWeak , 在上方分析 weak_unregister_no_lockweak_register_no_lock 的时候发现这两个方法内都是对指针地址的操作,但是方法内并没有进行加锁,所以在多线程操作的情况下,只是通过锁定地址来锁定排序问题,并没有对指针操作进行保护,所以这里是初始化和销毁 weak 为线程不安全。

10、补充一个图

image.png

这个图能看出通过 weak_table 的结构如下:

image.png

到这里 weak 的原理探析 就结束了,有问题欢迎大佬指出,共同学习,不断进步。

PS:可以运行的并且不断进行注释的objc 源码地址