iOS 内是通过引用计数来管理内存,引用计数的管理,很容易会出现“循环引用”问题。weak 修饰符,也是开发日常最常用的打破循环引用方式。被 weak 修饰符修饰的弱引用除了不会增加对象的引用计数外;在引用对象被释放后,这个弱引用会自动失效并置为 nil。本篇总结分析下 Objective-C 中 weak 都是怎么实现的。

分析源码基于:DeveloperErenLiu/RuntimeAnalyze/objc4-799.1

objc_initWeak()

在入口文件 KCObjcTest/main.m 中写入如下代码:

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
__weak id weakObj = obj;
}
return 0;
}

单步运行后,进入了 NSObject.mm 中的 objc_initWeak() 方法。在 runtime 源码中的实现如下:

id objc_initWeak(id *location, id newObj)
{
// 查看对象是否有效
// 无效对象立刻置空指针
if (!newObj) {
*location = nil;
return nil;
}

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

从源码中可以看到 objc_initWeak() 内部最后会调用storeWeak() 方法,传入了三个模板参数,自己理解的这段代码的意思是:

该弱引用不存在已有指向的对象(DontHaveOld),同时需要指向新的对象(DoHaveNew),如果目标对象正在释放就崩溃处理(DoCrashIfDeallocating)。

到了 storeWeak() 这一步,看下它的内部实现。

storeWeak()

storeWeak() 在 runtime 源码中的实现如下:

// 这里传递了三个 bool 数值
// 使用 template 进行常量参数传递是为了优化性能
/** HaveOld:
- true:变量有值
- false:需要被及时清理,当前值可能为 nil
HaveNew:
- true:需要被分配的新值,当前值可能为 nil
- false:不需要分配新值
CrashIfDeallocating:
- true:newObj 已经释放或 newObj 不支持弱引用,该过程需要暂停
- false:用 nil 代替存储
*/
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};

template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);

// 初始化 previouslyInitializedClass 指针
// 用于标记已经初始化的类
Class previouslyInitializedClass = nil;
id oldObj;

// 声明新旧 SideTable
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);

// 线程冲突处理
// 若有旧值,但 location 指向的对象地址不为 oldObj,那很可能被其它线程修改过
// 解锁并重试
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}

// 确保新值的 isa 已经调用 +initialize 初始化
// 避免弱引用机制和 +initialize 机制间的死锁
if (haveNew && newObj) {
// 获取新值的 isa
Class cls = newObj->getIsa();

// 若 newObj isa 与 previouslyInitializedClass 不同,
// 且 newObj 未被初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
// 解锁
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
// 初始化 newObj
class_initialize(cls, (id)newObj);

// 若 newObj 已经完成执行 +initialize,这是最理想情况
// 若这个 newObj 正在当前线程运行 +initialize
// 如在 +initialize 方法里对自己的实例调用了 storeWeak
// 需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记
previouslyInitializedClass = cls;

goto retry;
}
}

// 清理旧值
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}

// 设置新值
if (haveNew) {
// 把弱引用地址注册到 newObj 的弱引用条目
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// 如果 weakStore 操作应该被拒绝,weak_register_no_lock 会返回 nil
// 否则,对被引用对象设置弱引用标记位(is-weakly-referenced bit)
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}

// 之前不要设置 *location,这里需要更改指针指向
*location = (id)newObj;
}
else {
// 无新值,则不更改
}

// 解锁,让其他线程可以访问 oldTable, newTable
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

return (id)newObj;
}

上述代码可以看到,方法中核心的两个方法:weak_unregister_no_lockweak_register_no_lock。他们都是对 SideTable 的实例进行操作。实际上 SideTable 也是作为全局对象用于管理所有对象的引用计数和 weak 表,在 runtime 启动时和主线程的 AutoreleasePool 一起创建。

接着来看下 SideTable

SideTable

NSObject.mm 文件中 SideTable 结构体的源码为:

struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;

// ...
}

可以看到 SideTable 结构体主要的三个部分:

  • spinlock_t slock
    用于原子操作的自旋锁,用于给 SideTable 上锁和解锁
  • RefcountMap refcnts
    引用计数的 hash 表。仅在未开启 isa 优化或在 isa 优化开启且 isa_t 的引用计数溢出时才会用到。
  • weak_table_t weak_table
    弱引用指针的 hash 表。OC 中 weak 功能实现的核心数据结构。

前面的 storeWeak() 里,runtime 是通过如下方式获取对象的 SideTable

oldTable = &SideTables()[oldObj];

先看下 SideTables() 的源码实现:

class ExplicitInit {
alignas(Type) uint8_t _storage[sizeof(Type)];

public:
template <typename... Ts>
void init(Ts &&... Args) {
new (_storage) Type(std::forward<Ts>(Args)...);
}

Type &get() {
// reinterpret_cast 是 C++ 标准转换运算符
// 用来处理无关类型之间的转换,它会产生一个新的值
// 这个值会有与原始参数(_storage)有完全相同的比特位
return *reinterpret_cast<Type *>(_storage);
}
};

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

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

看到 SideTables() 返回的是一个 StripedMap 哈希表,以对象的地址作为键值返回对应的 SideTable

StripedMap

StripedMap 是一个模板类,定义于 objc-private.h 文件中,提供了一个以地址为键值的哈希结构。

template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
// ...

static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
// 哈希操作
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}

public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}

// ...

StripedMap 重定义了数组运算符,传入对象的地址,通过哈希运算获得对应内容。在 runtime 初始化后,会根据系统的不同,对应生成 8 或 64 个 SideTable 留作以后使用。

SideTable 里与弱引用有直接关系的是 weak 表。weak 表通过哈希表实现,将目标对象的地址作为键值进行检索以获得对应的弱引用变量地址。由于一个对象可同时赋值给多个弱引用变量,所以对于一个键值,可以注册多个弱引用变量的地址。

接着看下 weak_table 的实现。

weak_table

objc-weak.h 文件中 weak_table_t 的源码为:

struct weak_table_t {
// 弱引用条目列表
weak_entry_t *weak_entries;
// 弱引用条目的数量
size_t num_entries;
// 弱引用条目列表的大小
uintptr_t mask;
// 最大哈希偏移量
uintptr_t max_hash_displacement;
};

结构体中的 weak_entries 是一个动态列表,用来存储 weak_entry_t 类型的元素,需要对应到具体的内容。所以当出现冲突时还需要再处理,max_hash_displacement 就是用于出现冲突后辅助检查检索的内容是否存在。

weak_entry_t 的结构又是怎样的?

weak_entry_t

objc-weak.h 文件中 weak_entry_t 的源码为:

#define WEAK_INLINE_COUNT 4
#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;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
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& 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 的结构中,目标对象弱引用变量的指针都被封装在 DisguisedPtr 里。

同时用到了联合体,在联合体的内部有定长数组 inline_referrers[WEAK_INLINE_COUNT] 和动态数组 weak_referrer_t *referrers 两种方式来存储弱引用对象的指针地址。通过 out_of_line() 方法来判断采用哪种存储方式。当弱引用该对象的指针数目小于等于 WEAK_INLINE_COUNT 时,使用定长数组。当超过 WEAK_INLINE_COUNT 时,会将定长数组中的元素转移到动态数组中,且之后都是用动态数组存储。

结合前面可以知道:弱引用表的结构是一个哈希表,key 是所指对象的地址,value 是 weak 指针的地址(它的值是所指对象的地址)数组。

那么弱引用表是怎么维护这些数据的?

weak_register_no_lock()

objc-weak.mm 文件中 weak_register_no_lock 方法的源码为:

/** 
* 在弱引用表中查找对应的 weak_entry
* 若找到,则向其中插入 weak 指针地址
* 若未找到,新建一个 weak_entry
*
* @param weak_table 全局弱引用表,类型为 weak_table_t
* @param referent_id 弱指针
* @param referrer_id 弱指针地址
* @patam crashIfDeallocating 若被弱引用的对象正在析构,再次弱引用该对象是否 crash
*/
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;

// 如果 referent 为 nil
// 或 referent 是 TaggedPointer 计数方式,直接返回,不做任何操作
if (!referent || referent->isTaggedPointer()) return referent_id;

// 确保被引用的对象可用(不在析构,且支持 weak 引用)
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_table 中找到 referent 对应的 weak_entry
// 将 referrer 插入到 weak_entry_t 的引用数组中
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;
}

根据代码过一下 weak_register_no_lock() 的内部实现。

  • 首先判断 referent 是否为 nil 或 referent 是否用了 TaggedPointer 计数方式,如果是,直接返回不做任何操作。
  • 再判断对象是否在析构,若是,根据 crashIfDeallocating 判断是否抛出异常
  • 如果对象不能被 weak 引用,直接返回 nil
  • 当对象没有在析构且可以被 weak 引用,则调用 weak_entry_for_referent 方法根据 weak 指针从 weak_table 中查找对应的 weak_entry。如果找到,则调用 append_referrer方法,向 weak_entry 中插入 weak 指针地址 referrer;反之,新建一个 weak_entry

weak_entry_for_referent()

weak_entry_for_referent 方法主要是通过 weak 指针 referentweak_table 查找对应的 weak_entry_t。在 objc-weak.mm 文件中 weak_entry_for_referent 方法的源码为:

/** 
* Return the weak reference table entry for the given referent.
* If there is no entry for referent, return NULL.
* Performs a lookup.
*
* @param weak_table 弱引用表
* @param referent 弱指针,非 nil
*
* @return 返回查找到的 weak_entry_t
*/
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
ASSERT(referent);

weak_entry_t *weak_entries = weak_table->weak_entries;

if (!weak_entries) return nil;
// 通过 & weak_table->mask 位操作,确保 index 不会越界
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;
// 触发 bad weak table crash
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
// 当 hash_displacement(偏移量) 超过了 max_hash_displacement
// 说明元素不在 hash 表中,返回 nil
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}

return &weak_table->weak_entries[index];
}

append_referrer()

objc-weak.mm 文件中 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)
{
// 判断 weak_entry 是否使用动态数组
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;
}
}

// 若未插入,代表静态数组已存满
// 转换为动态数组
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;
}

ASSERT(entry->out_of_line());

// 如果动态数组中元素数 >= 数组总空间的3/4,则扩展数组空间为当前长度的一倍
// 扩容完成,插入
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
// 如果不需要扩容,直接插入
// '& (entry->mask)' 确保 begin 的位置只能大于或等于 mask (弱引用表大小)
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
// 用于记录 hash 偏移量
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;
}
// 存入并更新 num_refs
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}

weak_unregister_no_lock()

若 weak 指针之前指向了弱引用,则会调用 weak_unregister_no_lock 方法将旧的 weak 指针地址移除。在 objc-weak.mm 文件中 weak_unregister_no_lock 方法的源码为:

/** 
* Unregister an already-registered weak reference.
* This is used when referrer's storage is about to go away, but referent
* isn't dead yet. (Otherwise, zeroing referrer later would be a
* bad memory access.)
* Does nothing if referent/referrer is not a currently active weak reference.
* Does not zero referrer.
*
* FIXME currently requires old referent value to be passed in (lame)
* FIXME unregistration should be automatic if referrer is collected
*
* @param weak_table The global weak table.
* @param referent The object.
* @param referrer The weak reference.
*/
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;

// 查找到 referent 所对应的 weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 移除 referrer
remove_referrer(entry, referrer);
// 移除后,要检查一下 weak_entry_t 的 hash 数组是否已经空了
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;
}
}
}
// 如果 weak_entry_t 的 hash 数组为空
// 则需要将 weak_entry_t 从 weak_table 中移除
if (empty) {
weak_entry_remove(weak_table, entry);
}
}

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

上述即为对一个对象做弱引用时底层所做的处理。通过弱引用对象,不会使其引用计数加一。那当对象释放时,所有弱引用该对象的指针有时如何自动置为 nil 的?

dealloc

当对象的引用计数为 0 时,该对象会进行释放,对应的源码如下:

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

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

obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
// 判断对象是否为 TaggedPointer,是则直接 return
if (isTaggedPointer()) return; // fixme necessary?

// 如果对象是采用了优化的 isa 计数方式
// 且 对象没有被弱引用 !isa.weakly_referenced
// 且 没有关联对象 !isa.has_assoc
// 且 没有自定义的 C++ 析构方法 !isa.has_cxx_dtor
// 且 没有用到 SideTable 来引用计数 !isa.has_sidetable_rc
// => 则直接释放
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);
}
}

可以看到底层调用了 rootDealloc 方法。

object_dispose()

objc-runtime-new.mm 文件中 object_dispose 方法的源码为:

/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id
object_dispose(id obj)
{
if (!obj) return nil;

objc_destructInstance(obj);
free(obj);

return nil;
}

/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is 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();

// 如果有自定义的 C++ 析构方法,则调用 C++ 析构函数
if (cxx) object_cxxDestruct(obj);
// 如果有关联对象则移除关联对象
// 并将其自身从 Association Manager 的 map 中移除
if (assoc) _object_remove_assocations(obj);
// 清除对象的相关引用
obj->clearDeallocating();
}

return obj;
}

clearDeallocating()

objc-object.h 文件中 clearDeallocating 方法的源码为:

inline void 
objc_object::clearDeallocating()
{
// 判断对象是否采用了优化 isa 引用计数
if (slowpath(!isa.nonpointer)) {
// 没有,则清理对象存储在 SideTable 中的引用计数数据
sidetable_clearDeallocating();
}
// 当采用了优化 isa 引用计数,则判断
// 是否使用了 SideTable 的辅助引用计数 (isa.has_sidetable_rc)
// 或是否有 weak 引用 (isa.weakly_referenced)
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());
}

clearDeallocating_slow()

// Slow path of clearDeallocating() 
// for objects with nonpointer isa
// that were ever weakly referenced
// or whose retain count ever overflowed to the side table.
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));

// 在全局的 SideTables 中,通过 this 指针为key,找到对应的 SideTable
SideTable& table = SideTables()[this];
table.lock();
// 如果 obj 被弱引用
// 在 SideTable 的 weak_table 中对 this 进行清理工作
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
// 如果采用了 SideTable 做引用计数
// 在 SideTable 的引用计数中移除 this
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}

weak_clear_no_lock()

/** 
* Called by dealloc; nils out all weak pointers that point to the
* provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
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;

// 找出弱引用 referent 的弱指针地址数组以及数组长度
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) {
// 去除每一项对比置 nil 或报错
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();
}
}
}
// 由于 referent 要被释放了
// 因此 referent 的 weak_entry_t 也要移除出 weak_table
weak_entry_remove(weak_table, entry);
}

总结

weak 的实现原理在于底层维护了一份 weak_table_t 结构的哈希表,key 为所指对象的地址,value 为 weak 指针的地址数组。weak 关键字修饰的对象,代表弱引用,所引用对象的引用计数不会 +1,在引用对象被释放时会自动置为 nil。

对象释放的过程中,通过底层触发 clearDeallocating 函数方法,根据被释放对象地址通过查找获取得到所有 weak 指针地址的数组,之后逐个遍历将它们置为 nil,最后把相关的 entry 从 weak 表移除,最后清理对象的记录。

弱引用的核心部分有:SideTable、weak_table_t、weak_entry_t。关系图如下:

参考内容:

评论