iOS weak 底层实现原理
iOS weak 底层实现原理
weak的原理在于底层维护了一张weak_table_t
结构的hash表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。
weak 的实现原理可以概括一下三步:
- 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址;
- 添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
- 释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
相关类及函数调用关系图
SideTable、weak_table_t、weak_entry_t 之间的关系
如下图:
弱引用对象初始化流程
weak原理详解
当一个对象做weak引用,在运行时会在底层库调用objc_initWeak函数; 当对象的引用计数为0时,底层会调用_objc_rootDealloc方法对对象进行释放,而在_objc_rootDealloc方法里面会调用rootDealloc方法
objc_initWeak
该方法的两个参数location和newObj。 location :__weak指针的地址,存储指针的地址,这样便可以在最后将其指向的对象置为nil。 newObj :所引用的对象。即例子中的obj 。 在该方法内部调用了storeWeak方法。
storeWeak
这个方法的实现如下:
- storeWeak方法实际上是接收了5个参数,分别是haveOld、haveNew和crashIfDeallocating,这三个参数都是以模板的方式传入的,是三个bool类型的参数。 分别表示weak指针之前是否指向了一个弱引用,weak指针是否需要指向一个新的引用,若果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。
- 该方法维护了oldTable和newTable分别表示旧的引用弱表和新的弱引用表,它们都是SideTable的hash表。
- 如果weak指针之前指向了一个弱引用,则会调用weak_unregister_no_lock方法将旧的weak指针地址移除。
- 如果weak指针需要指向一个新的引用,则会调用weak_register_no_lock方法将新的weak指针地址添加到弱引用表中。
- 调用setWeaklyReferenced_nolock方法修改weak新引用的对象的bit标志位。
这个方法中的重点也就是weak_unregister_no_lock和weak_register_no_lock这两个方法。而这两个方法都是操作的SideTable这样一个结构的变量。
- SideTable SideTable 的定义:
struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; }
- spinlock_t slock : 自旋锁,用于上锁/解锁 SideTable。
- RefcountMap refcnts :用来存储OC对象的引用计数的 hash表(仅在未开启isa优化或在isa优化情况下isa_t的引用计数溢出时才会用到)。
-
weak_table_t weak_table : 存储对象弱引用指针的hash表。是OC中weak功能实现的核心数据结构。
- weak_table_t 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: hash数组,用来存储弱引用对象的相关信息weak_entry_t;
- num_entries: hash数组中的元素个数;
- mask:hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个);
-
max_hash_displacement:可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值); weak_table_t是一个典型的hash结构。weak_entries是一个动态数组,用来存储weak_entry_t类型的元素,这些元素实际上就是OC对象的弱引用信息。
- weak_entries
- weak_entry_t weak_entry_t的结构也是一个hash结构,其存储的元素是弱引用对象指针的指针, 通过操作指针的指针,就可以使得weak 引用的指针在对象析构后,指向nil。
#define WEAK_INLINE_COUNT 4 #define REFERRERS_OUT_OF_LINE 2 struct weak_entry_t { DisguisedPtr<objc_object> referent; // 被弱引用的对象 // 引用该对象的对象列表,联合。 引用个数小于4,用inline_referrers数组。 用个数大于4,用动态数组weak_referrer_t *referrers union { struct { weak_referrer_t *referrers; // 弱引用该对象的对象指针地址的hash数组 uintptr_t out_of_line_ness : 2; // 是否使用动态hash数组标记位 uintptr_t num_refs : PTR_MINUS_2; // hash数组中的元素个数 uintptr_t mask; // hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)素个数)。 uintptr_t max_hash_displacement; // 可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值) }; 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的结构定义中有联合体,在联合体的内部有定长数组inline_referrers[WEAK_INLINE_COUNT]和动态数组weak_referrer_t *referrers两种方式来存储弱引用对象的指针地址。
- 通过out_of_line()这样一个函数方法来判断采用哪种存储方式。
- 当弱引用该对象的指针数目小于等于WEAK_INLINE_COUNT时,使用定长数组。
- 当超过WEAK_INLINE_COUNT时,会将定长数组中的元素转移到动态数组中,并之后都是用动态数组存储。
weak_entry_for_referent 取元素; append_referrer 添加元素;
- weak_table_t weak_table_t 的定义:
- weak_register_no_lock weak_register_no_lock 方法添加弱引用。这个方法需要传入四个参数,它们代表的意义如下:
- weak_table:weak_table_t结构类型的全局的弱引用表。
- referent_id:weak指针。
- *referrer_id:weak指针地址。
- crashIfDeallocating :若果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。
该方法主要的做了如下几个方便的工作:
- 如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作。
- 如果对象正在析构,则抛出异常。
- 如果对象不能被weak引用,直接返回nil。
- 如果对象没有再析构且可以被weak引用,则调用weak_entry_for_referent方法根据弱引用对象的地址从弱引用表中找到对应的weak_entry,如果能够找到则调用append_referrer方法向其中插入weak指针地址。否则新建一个weak_entry。
- weak_unregister_no_lock weak_unregister_no_lock移除引用。如果weak指针之前指向了一个弱引用,则会调用weak_unregister_no_lock方法将旧的weak指针地址移除。
_objc_rootDealloc
rootDealloc
rootDealloc实现:
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
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);
}
}
- 首先判断对象是否是Tagged Pointer,如果是则直接返回。
- 如果对象是采用了优化的isa计数方式,且同时满足对象没有被weak引用!isa.weakly_referenced、没有关联对象!isa.has_assoc、没有自定义的C++析构方法!isa.has_cxx_dtor、没有用到SideTable来引用计数!isa.has_sidetable_rc则直接快速释放。
- 如果不能满足2中的条件,则会调用object_dispose方法。
-
object_dispose object_dispose方法很简单,主要是内部调用了objc_destructInstance方法。
- 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(); } return obj; }
这段代码表示:如果有自定义的C++析构方法,则调用C++析构函数。如果有关联对象,则移除关联对象并将其自身从Association Manager的map中移除。调用clearDeallocating方法清除对象的相关引用。
- clearDeallocating
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()); }
clearDeallocating中有两个分支,现实判断对象是否采用了优化isa引用计数。
- 如果没有的话则需要清理对象存储在SideTable中的引用计数数据。
-
如果对象采用了优化isa引用计数,则判断是都有使用SideTable的辅助引用计数(isa.has_sidetable_rc)或者有weak引用(isa.weakly_referenced),符合这两种情况中一种的,调用clearDeallocating_slow方法。
-
clearDeallocating_slow 在这个方法里面调用了weak_clear_no_lock来做weak_table的清理工作。
- weak_clear_no_lock
-
- clearDeallocating
- objc_destructInstance
此处更详细参考:iOS底层原理:weak的实现原理