在 iOS 开发中,weak property 或者是 __weak
修饰的变量,在对象释放后,变量会自动置为 nil。delegate 模式、block 中 weak strong dance 都会用到。
比如,声明一个 weak 的 delegate。
1 | @property (nonatomic, weak) id delegate; |
weak 用的好可以避免内存泄漏和野指针崩溃。weak 有这么大的作用,底层具体是咋实现的呢?本文探讨下 weak 的底层实现原理。
初探 weak 实现
首先通过汇编来看下,底层怎么实现的。先写几行代码。
1 | { |
在 Xcode 中勾选 Debug -> Debug Workflow -> Always Show Disassembly,然后在 NSLog 行加个断点,运行之后可以看到这样一段汇编代码:
这里可以看到 3 个 weak 相关的函数,很容易对应到刚刚的三行代码中。
1 | { |
知道这三个函数那就好说了,从 github 上可以下载到 runtime 的源码来研究。GitHub - opensource-apple/objc4
底层存储结构
这里不过多的粘贴源码,简单介绍下 weak 底层的存储结构。
先解释两个单词,referent 是指 weak 变量指向的对象,referrer 是指 weak 变量。
最外层是一个 StripedMap
,以 referent 实例地址作为 key,通过哈希,平均映射到 64 个 SideTable
中。SideTable
中最关键的一个成员是 weak_table
,weak_table
的成员 weak_entries
是一个 weak_entry_t
结构体数组,每一个 weak_entry_t
结构体都保存着 referent 地址和指向这个 referent 的所有 weak 变量地址,也就是 referrers 数组。
runtime 源码解释
了解了结构,再来单独聊聊刚刚在汇编中看到的几个函数。
objc_initWeak
通过 referent 去找 SideTable,再遍历weak_entries
找到对应的 weak entry(weak_entry_t
结构体),将 weak 变量地址添加到对应的 referrers 数组中。当然,如果没有找到 weak entry,会创建一个。objc_loadWeak
本身 weak 变量已经指向了 referent。objc_loadWeak
内部检查是否是 tag pointer、是否允许 weak reference 等等条件。并查找是否有对应的 weak entry,如果能找到且各种条件满足,则返回 referent 地址,否则返回 nil。
⚠️ 这里返回的 referent 地址,在 runtime 层已经 retain & autorelease。
objc_destroyWeak
这个函数就是objc_initWeak
的反向操作,把 weak 变量指针从 referrers 数组中移出。如果 referrers 数组为空,那么也顺带会移除 weak entry。
讲了这么多,那这些 weak 指针置为 nil 的逻辑在哪里呢?
从 NSObject 的 dealloc 源码入手,可以看到最后调用到了 weak_clear_no_lock
方法。
weak_clear_no_lock
这个函数主要逻辑是找到对应的 weak entry,遍历 referrers 数组,将所有的 weak 变量都置为 nil,再将 weak entry 移除掉。