iOS 底层探究之 weak引用

851 阅读15分钟

#iOS 底层探究之 weak引用

关键字weak的使用在iOS开发中,可以说最常用之一。delegate、block、NSTimer中的循环引用问题,我们都需要使用weak关键词.下面我们来探讨下weak的底层逻辑是如何的。

weak的初始化

我们在使用weak时常常如下代码:

@property(nonatomic, weak) id<UITableViewDelegate> *delegate;

__weak NSObject *weakObj = obj;

如果只是从这样看weak,我们不知道其底层做了如何操作。我们可以在如下操作:

  1. 首先在 __weak 修饰变量那行加上断点: 6.1_weak_init_debug.png
  2. 然后开启汇编debug,这样我们看出底层做了什么操作,而且XCode在汇编debug会加上一些关键注释。注意: 需要关闭其他注释,不然会影响此次debug。 6.2_weak_debug_disassembly.png
  3. 然后查看汇编debug, 看下面图片标记的1,该行汇编对应是__weak NSObject *weakObj = obj;,所以后续的2和3是runtime对__weak修饰的变量的操作。Runtime首先会调用objc_initWeak方法,在调用storeWeak方法。 6.3_weak_disassembly.jpg
  4. 然后我们在objc源码中在objc_initWeak()方法中打上断点,然后移除debug汇编,点控制台上的“continue program execution”,然后就跳转到objc_initWeak()方法体中。
  5. objc_initWeak方法通过传入一个location指针和obj对象来初始化,location指针指向__weak修饰的变量。然后调用storeWeak()方法.
/** 
 * 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.)
 *
 * @param location Address of __weak ptr.  weak指针
 * @param newObj Object ptr.    obj对象
 */
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }
    // location: weakObj, newObj: 源对象
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
  1. 从这就开始了weak指针的初始化。

storeWeak():Runtime弱引用对象逻辑

  1. 首先从SideTables中获取oldObj/newObj的weak table. oldObj即弱引用对象开始弱引用的目标对象,newObj当前弱引用的目标对象。
  2. 检查newObj对象的类型是否初始化,没有则先对其初始化,然后再从第一步重新开始
  3. 如果oldObj不为nil,则从oldObj的弱引用结构体中将location移除
  4. 如果newObj不为nil,则将location插入到newObj的弱引用结构体中 4.1 设置newObj的weakly_referenced=true,标记newObj被弱引用了,前提是newObj不是tagged Pointer或nil 4.2 *location = obj, 这样weakObj则和obj是同一个内存地址了,也没有对obj做retain操作。
  5. 调用newObj的_setWeaklyReferenced()方法,重置obj的weakly_referenced值。
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
          enum 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; // oldObj的weak table
    SideTable *newTable; // newObj的weak table

    // 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:
    // 1. 获取oldObj的weak table
    if (haveOld) { // 如果location原来有值,即weakObj从旧值指向新值
        oldObj = *location;
        oldTable = &SideTables()[oldObj]; // 获取oldObj的weak table
    } else {
        oldTable = nil;
    }
    
    // 2. 获取newObj的weak table
    if (haveNew) { // 如果newObj有值
        newTable = &SideTables()[newObj];
    } else { // 将weakObj置为nil
        newTable = nil;
    }

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

    if (haveOld  &&  *location != oldObj) { // 如果有旧值,解锁oldObj的table和newObj的Table
        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.
    // 3. 判断newObj的类型是否初始化,没有则初始化,初始化后,在重新回到步骤1
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa(); // 获取源对象的isa
        // 如果源对象的class没有初始化,则进入初始化
        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.
    // 4. 如果有旧值,需要从oldObj中移除weak指针
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    // 5. 如果weakObj是弱引用一个新的Obj,则需要向newObj中注册
    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.
        // 5.1 如果newObj不是tagged pointer或nil,则设置weakly_referenced=true,标记newObj被弱应用了
        if (!_objc_isTaggedPointerOrNil(newObj)) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        // 5.2weakObj指针指向newObj,即weakObj和newObj是同一个指针
        *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.
    // 6. 调用_setWeaklyReferenced(), 重置obj的weakly_referenced的值
    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}

从oldObj中移除弱引用对象

  1. 调用weak_unregister_no_lock()方法从obj中移除一个弱引用对象
    1. 首先获取obj的弱引用结构体
    2. 从弱引用结构体中移除弱引用指针
    3. 如果弱引用结构体没有任何弱引用数据,则从weak table中移除该结构体
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;

    // 1. 首先获取obj的弱引用结构体
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        // 2. 从弱引用结构体中移除该弱引用
        remove_referrer(entry, referrer);
        bool empty = true;
        // 判断弱引用结构体中是否有弱引用数据
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }

        // 3. 如果弱引用结构体中没有弱引用数据,则将其从weak table中移除
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }

    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}
  1. 弱引用结构体: 2.1 referent: 弱引用的目标对象,以它为key,存储在weak table中 2.2 out_of_line_ness 用来计算是否即将存满,一般是集合的3/4 2.3 num_refs已存储的弱引用个数,和out_of_line_ness用来判断弱引用集合是否即存放满 2.4 inline_referrers保存弱引用指针的集合 2.5 weak_referrer_t: 保存弱应用地址,该指针是伪装存储的,所以内存分析工具不会看到很多从弱表到对象的内部指针。
#define REFERRERS_OUT_OF_LINE 2

struct weak_entry_t {
    DisguisedPtr<objc_object> referent; // 弱引用的目标对象
    union {
        struct {
            weak_referrer_t *referrers;  // 弱引用结合
            uintptr_t        out_of_line_ness : 2; // 用来计算是否即将存满,一般是集合的3/4
            uintptr_t        num_refs : PTR_MINUS_2; // 已存储的弱引用个数
            uintptr_t        mask;  // hash计算时的求余的被除数
            uintptr_t        max_hash_displacement; // hash冲突时的移动数,为0,
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT]; // 弱引用数组
        };
    };

    // 是否即将存满
    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    // 判断两个weak_entry_t是否相等
    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    /// 初始化weak_entry_t
    /// @param newReferent 指向目标对象的指针
    /// @param newReferrer 指向弱引用指针的指针
    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;
        }
    }
};

3.弱应用指针:

// 弱引用变量的地址。
// 这些指针是伪装存储的,所以内存分析工具不会看到很多从弱表到对象的内部指针。
typedef DisguisedPtr<objc_object *> weak_referrer_t;

4.获取obj的弱引用结构体, 首先获取weak table下弱引用结构体集合,然后遍历该集合,如果弱引用结构体的目标对象和当前目标对象是否相等,如果相等则退出循环,并范湖该结构体。

/// 从weak table中获取obj的弱引用结构体
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    ASSERT(referent);

    // 获取weak table下所有的弱引用结构体集合
    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

    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;
        }
    }
    
    // 返回obj的弱引用结构体
    return &weak_table->weak_entries[index];
}

5.从obj的弱引用结构体中移除弱引用指针:遍历弱引用集合(链表),查找到弱引用指针存储的位置,然后将对应位置的元素设置为nil(注意: 不是弱引用对象设置为nil),并设置存储弱引用个数-1。 5.1 弱引用集合的元素指向弱引用指针,弱引用指针指向目标对象

/// 移除目标对象的弱引用对象
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    // 判断是否存满
    if (! entry->out_of_line()) {
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            // 找到弱引用指针元素,将该元素置为nil
            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,注意不是将弱引用置为nil
    entry->referrers[index] = nil;
    entry->num_refs--; // 弱引用个数-1
}

6.如果弱引用集合为空,则将弱引用结构体从weak table中移除。

/// 从weak table中移除obj的弱引用数据
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // remove entry
    // 释放弱引用集合
    if (entry->out_of_line()) free(entry->referrers);
    bzero(entry, sizeof(*entry));

    // weak table中的弱应用对象个数-1
    weak_table->num_entries--;

    // 检查weak table内存空间,如果大部份为nil,则分配新空间存储当前值,释放原有空间
    weak_compact_maybe(weak_table);
}

7.如果weak table存储的元素个数大于1024,或者小于其集合的1/16,则重新分配内存空间.

// Shrink the table if it is mostly empty.
// 如果weak table大部份元素为nil,则重新分配内存空间
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.
    // 若存储元素个数大于1024其存储的数据小于1/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
    }
}
  1. 为weak table分配新的内存空间
/// 为weak table分配新的内存空间
static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // 获取weak table中的弱引用存储的旧数据
    weak_entry_t *old_entries = weak_table->weak_entries;
    // 为weak table中存储的弱引用数据开辟新的内存空间
    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;  // 最大hash值移动
    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; // 获取weak table中最后一个元素
        // 将原来的weak table中的数据存储到新的内存中
        for (entry = old_entries; entry < end; entry++) {
            if (entry->referent) {
                weak_entry_insert(weak_table, entry);
            }
        }
        free(old_entries); // 释放原来的weak table数据
    }
}

向obj插入弱引用对象

1.通过weak_register_no_lock()向obj插入弱引用对象,首先获取obj的弱引用结构体,如果存在则直接插入弱引用指针;否则新建一个弱引用结构体,在插入到weak table中。

/** 
 * Registers a new (object, weak pointer) pair. Creates a new weak
 * object entry if it does not exist.
 * 
 * @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; // 弱引用对象

    if (_objc_isTaggedPointerOrNil(referent)) return referent_id;

    // ensure that the referenced object is viable
    if (deallocatingOptions == ReturnNilIfDeallocating ||
        deallocatingOptions == CrashIfDeallocating) {
        bool deallocating;
        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.
            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));
        }

        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);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry); // 向weak table中插入一个元素(目标对象的弱引用结构体)
    }

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

    return referent_id;
}
  1. 新建弱引用结构体对象:向obj第一次插入弱引用对象时,弱引用结构体是空的,所以需要新建一个弱应用结构体对象。
weak_entry_t new_entry(referent, referrer);
  1. 其次判断obj所在的weak table是否已经存满, 如果已经存储3/4的控件,则扩容weak table的内存空间,扩容大小为原来的2倍。
// 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.
    // 对weak table扩容
    if (weak_table->num_entries >= old_size * 3 / 4) {
        weak_resize(weak_table, old_size ? old_size*2 : 64);
    }
}
  1. 向weak table中插入弱引用结构体:
/// 向weak table中插入弱引用结构体
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
    // 获取weak table的弱应用结构体集合
    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;
    // 获取首个不为nil的元素位置
    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++; // 更新存储元素个数

    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}
  1. 当obj有弱引用结构体对象时,直接插入弱引用对象: 5.1 如果弱引用结构体的弱引用集合没有存满,则直接存入 5.2 如果弱引用结合即将存满(即达到或超过3/4),则进行2倍扩容,在存入弱引用对象
/// 向弱引用结构体中插入一个弱引用指针
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) { // 如果弱引用结构体的弱引用集合没有存满
        // Try to insert inline.
        // 将弱引用指针插入到集合中,找到第一个指向为nil的元素,将该元素指向弱引用指针
        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.
        // 如果不能线性插入,则创建线性内存空间
        // 一般不会执行这里
        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.
        // 将原元素插入到新的内存中
        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;    // 弱引用大小
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;  // 需要扩容的元素个数
        entry->mask = WEAK_INLINE_COUNT-1;  // hash计算的mask值
        entry->max_hash_displacement = 0;   // hash冲突时移动的大小
    }

    ASSERT(entry->out_of_line());

    // 如果超过弱引用集合的3/4, 则进行2倍扩容
    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);
    }
    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++; // 弱引用结构体已保存弱引用个数
}
  1. 如果超过弱引用集合的3/4, 则进行2倍扩容
/// 为弱引用集合扩容
__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);
    // 设置新的弱引用集合大小为原来的两倍,如果原弱引用集合大小为负数时,大小为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; // 获取原弱引用集合
    entry->mask = new_size - 1; // 设置弱引用结构体的hash mask
    
    // 为新弱引用结合分配空间
    entry->referrers = (weak_referrer_t *)
        calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
    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--; // 原来存放的弱引用个数,计算还有多少个未插入多新的弱引用集合中
        }
    }
    // Insert
    // 设置弱引用结构体的弱引用集合
    append_referrer(entry, new_referrer);
    if (old_refs) free(old_refs); // 释放旧弱引用集合
}

Obj释放时,weakObj被置为nil

我们使用weak来打破循环引用的重要原因之一,是当obj被释放后,weakObj会自动置为nil,我们来查看下weakObj是如何置为nil的。 查看如下代码:

NSObject *obj = [NSObject new];
__weak NSObject *weakObj = obj;
obj = nil;
  1. 在obj 设置为nil前,会先对obj进行释放操作逻辑,会调用NSObject的 -dealloc方法,然后再对weak引用释放。

6.6_weak_obj释放逻辑.png

  1. deallock 经过多层调用最后会objc_object::clearDeallocating_slow()方法中判断obj是否有弱引用(isa.weakly_referenced == true),如果为真,则调用 weak_clear_no_lock()方法释放weak引用,并置为nil.
  2. weak_clear_no_lock() 逻辑:
  • 1.获取弱引用结构体
  • 2.获取弱引用链表
  • 3.将弱引用链表节点指向的弱引用指针置为nil,这样节点指向的内容也是nil
  • 4.从weak table中移除obj的弱引用结构体
// 清除weak指针,并将weakObj置为nil
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
    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;
    }
    
    // 遍历弱引用链表,即弱引用指针置为nil,这样节点指向的内存也是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中移除
    weak_entry_remove(weak_table, entry);
}
  1. 这样完成了弱引用置为nil的逻辑。

总结

  1. 调用objc_initWeak()初始化指向弱引用对象的指针
  2. 调用storeWeak()将弱引用指针与obj关联起来。
  • 弱引用指针是否有旧值
    1. 获取oldObj目标对象在weak table中的弱引用结构体
    2. 从旧目标对象的弱引用结构体对象中移除弱引用指针 2.1 将弱引用结构体的弱引用集合指向该弱引用指针的元素置为nil 2.2 如果弱引用结构体中的弱引用集合为空,则将弱引用结构体从weak table中移除
  • 将弱引用指针和newObj关联起来
    1. 获取newObj目标对象在weak table中的弱引用结构体
    2. 如果弱引用结构体为空,则使用目标对象和弱引用指针新建一个弱引用结构体,并将该结构体插入到weak table中
    3. 如果弱引用结构体不为空,则直接将弱引用指针插入到该弱引用结构体中
    4. 如果弱引用结构体已经使用的空间超过3/4, 先对其进行扩容,扩容规则为原来的两倍,在插入新的弱引用对象
  • 如果obj被释放时,会在释放前,将weak引用都置为nil 1.获取弱引用结构体 2.获取弱引用链表 3.将弱引用链表节点指向的弱引用指针置为nil,这样节点指向的内容也是nil 4.从weak table中移除obj的弱引用结构体
  1. weak在Runtime中的逻辑流程图: 6.4_weak逻辑.jpg
  2. weak指针的内存结构图: 6.5_weak_弱引用内存关系.png