逐步探究ObjC的Weak技术底层

1,166 阅读11分钟

前言

之前的文章有说过 Atomic 原子操作的原理,其作为一个特殊的修饰前缀,影响了存取操作。

在属性修饰定义中,还有另一类修饰前缀,他们分别是 strong weak assign copy,这些又有什么区别呢?

平时喜欢探究的同学,可能也见过 unsafe_unretained,这个又是什么呢?

让我们从属性修饰入手,逐步揭开弱引用的面纱。


原理

属性自动生成的实现方法是怎么样的?

首先我们先创建一个示例代码文件作为样本。

#import <Foundation/Foundation.h>

@interface PropertyObject : NSObject

@property (nonatomic, strong) NSObject *pStrongObj; //强引用
@property (nonatomic, copy)   NSObject *pCopyObj;   //拷贝
@property (nonatomic, weak)   NSObject *pWeakObj;   //弱引用
@property (nonatomic, assign) NSObject *pAssignObj; //申明
@property (nonatomic, unsafe_unretained) NSObject *pUnretainedObj; //非持有

@end

@implementation PropertyObject
@end

然后通过 clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.14 -fobjc-runtime=macosx-10.14 -Wno-deprecated-declarations main.m 命令将其解释成 c++ 代码。(注意这里要指定版本,不然weak属性不能翻译)

展开的代码比较多,我这里截取关键部分探讨。

struct PropertyObject_IMPL {
	NSObject *__strong _pStrongObj;
	NSObject *__strong _pCopyObj;
	NSObject *__weak _pWeakObj;
	NSObject *__unsafe_unretained _pAssignObj;
	NSObject *__unsafe_unretained _pUnretainedObj;
};

{"pStrongObj","T@\"NSObject\",&,N,V_pStrongObj"},
{"pCopyObj","T@\"NSObject\",C,N,V_pCopyObj"},
{"pWeakObj","T@\"NSObject\",W,N,V_pWeakObj"},
{"pAssignObj","T@\"NSObject\",N,V_pAssignObj"},
{"pUnretainedObj","T@\"NSObject\",N,V_pUnretainedObj"}

从变量结构体的描述和特性可以看出,strongcopy实际都是__strong修饰,但特性不同,assignunsafe_unretained 则完全一致,都是__unsafe_unretainedweak则单独使用__weak修饰。

下面我们来看一下方法具体实现。

// @implementation PropertyObject

//根据偏移取值和赋值
static NSObject * _I_PropertyObject_pStrongObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pStrongObj)); }
static void _I_PropertyObject_setPStrongObj_(PropertyObject * self, SEL _cmd, NSObject *pStrongObj) { (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pStrongObj)) = pStrongObj; }

static NSObject * _I_PropertyObject_pCopyObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pCopyObj)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

//只有Copy不同,setter的实现是objc_setProperty
static void _I_PropertyObject_setPCopyObj_(PropertyObject * self, SEL _cmd, NSObject *pCopyObj) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct PropertyObject, _pCopyObj), (id)pCopyObj, 0, 1); }

static NSObject * _I_PropertyObject_pWeakObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__weak *)((char *)self + OBJC_IVAR_$_PropertyObject$_pWeakObj)); }
static void _I_PropertyObject_setPWeakObj_(PropertyObject * self, SEL _cmd, NSObject *pWeakObj) { (*(NSObject *__weak *)((char *)self + OBJC_IVAR_$_PropertyObject$_pWeakObj)) = pWeakObj; }

static NSObject * _I_PropertyObject_pAssignObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pAssignObj)); }
static void _I_PropertyObject_setPAssignObj_(PropertyObject * self, SEL _cmd, NSObject *pAssignObj) { (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pAssignObj)) = pAssignObj; }

static NSObject * _I_PropertyObject_pUnretainedObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pUnretainedObj)); }
static void _I_PropertyObject_setPUnretainedObj_(PropertyObject * self, SEL _cmd, NSObject *pUnretainedObj) { (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pUnretainedObj)) = pUnretainedObj; }
// @end

在代码中,只有copy修饰属性的setter方法使用了objc_setProperty,其他几种都是根据 self + 偏移量 的方式计算出内存地址直接进行存取。

那问题来了,如果真的是那么简单的话,arc 是怎么实现根据不同修饰从而进行内存管理的呢?

原来通过 clang -rewrite-objc 的代码只是翻译成 c++ 语言,在之后的编译过程中会进一步处理。

接着使用 clang -S -fobjc-arc -emit-llvm main.m -o main.ll 命令生成中间码。

(中间码显示比较杂乱,我根据自己理解整理成简洁版)

//代码整理后
id [PropertyObject pStrongObj] {
  return *location; 
}
void [PropertyObject setPStrongObj:](self, _cmd, obj) {
  @llvm.objc.storeStrong(*location, obj)
}

id [PropertyObject pCopyObj] {
  return @objc_getProperty(self, _cmd, offset, atomic)
}
void [PropertyObject setPCopyObj:](self, _cmd, obj) {
  @objc_setProperty_nonatomic_copy(self, _cmd, obj, offset)
}

id [PropertyObject pWeakObj] {
  id obj = @llvm.objc.loadWeakRetained(*location)
  return @llvm.objc.autoreleaseReturnValue(obj)
}
void [PropertyObject setPWeakObj:](self, _cmd, obj) {
  @llvm.objc.storeWeak(*location, obj)
}

id [PropertyObject pAssignObj] {
  return *location
}
void [PropertyObject setPAssignObj:](self, _cmd, obj) {
  *location = obj
}

id [PropertyObject pUnretainedObj] {
  return *location
}
void [PropertyObject setPUnretainedObj:](self, _cmd, obj) {
  *location = obj
}

可以看出分别针对strongweak 都做了处理,而assignunsafe_unretained则不做内存管理直接返回,这也说明这两者的处理方式是一样的,区别在于 assign 针对。

strong copy weak assign unsafe_unretained
Ownership __strong __strong __weak __unsafe_unretained __unsafe_unretained
Getter *location objc_getProperty loadWeakRetained *location *location
Setter storeStrong objc_setProperty storeWeak *location *location
对象 NSObject NSObject NSObject NSObject Scalar

Weak对象怎么实现存取的?

本文篇幅有限,暂不介绍 storeStrongobjc_setProperty_nonatomic_copy,主要介绍 weak 相关操作。

打开 objc4-750 开源代码,翻到 NSObject.mm,我们来一探究竟。

// 初始化弱引用
id objc_initWeak(id *location, id newObj) {
    // 不存在则不保存
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
// 销毁弱引用
void objc_destroyWeak(id *location) {
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}
// 交换原有的值
id objc_storeWeak(id *location, id newObj) {
    return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object *)newObj);
}

可以看到 runtime 中调用的都是一个方法,区别在于使用了不同的模版,那么我们来看下对一个地址的存取方法。

// 获取操作的具体实现
id objc_loadWeakRetained(id *location) {
    id obj;
    id result;
    Class cls;

    SideTable *table;
    
 retry:
    // 保证地址有数据且不是伪指针
    obj = *location;
    if (!obj) return nil;
    if (obj->isTaggedPointer()) return obj;
    // 根据地址取出对应的表
    table = &SideTables()[obj];
    // 加锁
    table->lock();
    // 如果数据被其他线程改变,则重试
    if (*location != obj) {
        table->unlock();
        goto retry;
    }
    result = obj;

    cls = obj->ISA();
    if (! cls->hasCustomRR()) {
        // 如果使用的是系统默认的内存管理,则保证了已经初始化
        // 所以可以直接rootTryRetain
        assert(cls->isInitialized());
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    } else {
        // 如果不是默认的,则需要确保在初始化线程上执行自定义retain操作
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                class_getMethodImplementation(cls, SEL_retainWeakReference);
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            } else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
                result = nil;
            }
        } else {
            table->unlock();
            _class_initialize(cls);
            goto retry;
        }
    }
    //完成后解锁
    table->unlock();
    return result;
}
// 保存操作的具体实现
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;
    }

    // 检查新对象类有没有初始化完,没有则重试
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            // 如果正在初始化,则让下一次绕过这个判断继续运行
            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);
        // 保存成功就记录到对象指针中,这样可以在释放时检查
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // 保存到对应位置
        *location = (id)newObj;
    }
    
    // 操作成功后解锁
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    // 返回最终数据
    return (id)newObj;
}

除去保护方法,其实 objc_loadWeakRetained 方法就是检查后返回 *location,也就是变量指向的实际地址。

storeWeak 方法则是根据模版,对旧对象执行 weak_unregister_no_lock,对新对象执行 weak_register_no_lock

//注销引用
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;

    //获取被引用人的引用数组
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //移除引用人
        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;
                }
            }
        }
        //如果一个引用也没了,则删除节点
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }

    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
    // 上面为苹果注释,看这意思应该是objc_storeWeak还需要使用引用地址做后续处理。
}
//注册引用
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;   //引用人
    // taggedPointer没有引用计数,不需要处理
    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, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_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;
    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);
    }

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

    return referent_id;
}
//释放过程清空引用
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) {
        //这里应该肯定有entry,因为调用前判断了对象的WeaklyReferenced
        //如果确实没有,苹果认为可能是CF/objc原因
        return;
    }

    //清空引用数组
    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();
            }
        }
    }
    //去除节点
    weak_entry_remove(weak_table, entry);
}

可以发现,对申明是 __weak 的变量进行存取操作,其实都是通过被操作的对象地址查找到相应的表,然后增删表的引用数组内容。

SideTable表怎么设计的?

关键就在于怎么申明创建表,以及这个表是怎么设计及使用的。

// SideTables 类型申明
// 这里之所以先使用数据的方式申明是因为考虑到加载顺序的问题
alignas(StripedMap<SideTable>) static uint8_t 
    SideTableBuf[sizeof(StripedMap<SideTable>)];
// 加载image时执行初始化
static void SideTableInit() {
    new (SideTableBuf) StripedMap<SideTable>();
}
// 数组还原成StripedMap类型
static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

// StripedMap 的结构
enum { CacheLineSize = 64 };
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif
    // 64位对齐
    struct PaddedT {
        T value alignas(CacheLineSize);
    };
    // 手机系统数组个数为8
    PaddedT array[StripeCount];
    // 把指针地址匹配到数组的序号
    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }
}

在加载镜像的过程中,通过 SideTableInit 方法创建全局表数组,可以看到手机系统是8个数组。

源码中使用 &SideTables()[obj] 的方式,其实就是把 obj 的指针地址转成序号获取某一个 table,通过这种方式分散冗余。

接着我们看 SideTable 类的内部结构。

// 哈希散列表,使用补码的形式把指针地址作为Key,保存引用计数
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;

// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };

struct SideTable {
    spinlock_t slock;       // 自旋锁
    RefcountMap refcnts;    // 引用记数表
    weak_table_t weak_table;// 弱引用表

    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

struct weak_table_t {
    weak_entry_t *weak_entries;     //弱引用数组
    size_t    num_entries;          //数组个数
    uintptr_t mask;                 //计算辅助量,数值为数组总数-1
    uintptr_t max_hash_displacement;//哈希最大偏移量
};

#if __LP64__
#define PTR_MINUS_2 62
#else
#define PTR_MINUS_2 30
#endif

typedef DisguisedPtr<objc_object *> weak_referrer_t;
struct weak_entry_t {
    // 被引用者
    DisguisedPtr<objc_object> referent;
    union {
        // 引用者数据结构
        struct {
            // 当数量超过4个时,结构转为指针,每次容量满的时候就扩容两倍
            // 需要与数组作区分,所以有out_of_line_ness标记
            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 {
            // 四个数组
            weak_referrer_t  inline_referrers[4];
        };
    };
};

SideTable 存储的不仅有对象引用计数表,还有我们关注的弱引用表,其结构顺序如下:

SideTable->weak_table_t->weak_entry_t->weak_referrer_t

为了方便理解,我模拟一下找弱引用对象的步骤:

  1. sideTable = &SideTables()[referent] 把对象内存地址按照8取余后找到表

  2. weakTable = &sideTable->weak_table 取出弱引用表

  3. entry = weak_entry_for_referent(weakTable, referent) 根据被引用人地址,遍历弱引用表找出入口

  4. referrer = entry->referrers[index] 入口有特殊的数组,其中保存了所有弱引用者的对象地址

仔细一点的同学应该发现了 weak_entry_t 中有一个联合体,这又是怎么操作实现的呢?

// 添加新引用者
static void append_referrer
    (weak_entry_t *entry, objc_object **new_referrer) {
    // 没有超过4个,就用内敛数组
    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;
            }
        }

        // 如果超过4个了,就从数组结构转成指针结构
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // 拷贝原数据到指针指向的内容
        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;               //数组最大下标,用于取余
        entry->max_hash_displacement = 0;                //最大hash移位次数,用于优化循环
        // 由于只有4个,会在下个判断后执行grow_refs_and_insert初始化并插入新对象
    }
    // 断言必然是指针结构
    assert(entry->out_of_line());
    // 如果指针数量超过3/4,就容量翻倍后再插入
    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; //下标+1后取余
        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++;
}

总结

至此对于弱引用的整体结构和逻辑都清楚了,对象根据修饰符进行内存管理,如果是弱引用,则找到其引用地址的引用表操作。

反过来讲,强对象被引用时在全局引用表中注册一个节点,保存所有引用者的地址,当释放时设置所有地址为空。


问答

被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?

对象被释放时执行 obj->rootDealloc(),如果有弱引用标记,则会执行 objc_destructInstance 方法后释放。

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();                   //处理isa
    }

    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());
}
void objc_object::sidetable_clearDeallocating() {
    SideTable& table = SideTables()[this];

    // 删除强引用和弱引用
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}

可以看到在 sidetable_clearDeallocating 方法中,最后执行了 weak_clear_no_lock 清空了所有引用关系。

SideTable 表结构如下图:


总结

weak原理是绕不开的经典课题,通过阅读开源代码对苹果如何实现有了大致的了解,受益匪浅。

阅读过程中还惊叹于苹果各种花式小技巧,由于文章篇幅有限没来得及介绍,感兴趣可以了解一下,比如 DisguisedPtr

资料分享

Objective-C Class Ivar Layout 探索

理解 ARC 实现原理

weak 弱引用的实现方式