Weak实现原理|青训营笔记
这是我参与「第四届青训营 」笔记创作活动的第5天
__weak 修饰的对指针在引用对象时,不会增加引用对象的计数,并在引用对象计数归0销毁之后 __weak 修指针会自动将指向对象置为nil。为什么 __weak 可以实现这样的效果
id object = [[NSObject alloc] init];
{
id __weak objc = obj;
}
初始化时,会调用 objc_initWeak(&objc, object) 方法
objc_initWeak(id *loacation, id newObj)
方法底层源码
/// 初始化__weak修饰符修饰对象
/// @param location: __weak指针的地址,即存储指针的地址,方便在最后的时候将其指向的地址置为nil
/// @param newObj:所引用对象
id objc_initWeak(id *location, id newObj){
//如果引用对象本来就是空,就直接让 __weak指针指向nil
if (!newObj) {
*location = nil;
return nil;
}
//否则调用storeWeak方法
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
storeWeak方法
**template <HaveOld haveOld, HaveNew haveNew, CrashIfDeallocating crashIfDeallocating> static id storeWeak(id location, objc_object newObj)
源代码:有些长,但是配合下面的实现讲解会容易理解一下
核心实现功能
1.将weak指针的地址 location 存入到obj对应的 weak_entry_t 的数组(链表)中,用于在obj析构时,通过该数组(链表)找到所有其weak指针引用,并将指针指向的地址(location)置为nil。
2.如果启用了isa优化,则将obj的 isa_t 的 weakly_referenced 位置为1。置为1的作用主要是为了标记obj被weak引用了,当dealloc时,runtime会根据 weakly_referenced 标志位来判断是否需要查找obj对应的 weak_entry_t ,并将引用置为nil。
实现步骤
1.storeWeak实际上接收了五个参数: haveOld、haveNew 、crashIfDeallocating 、loacation 、 newObj
- haveOld : bool类型,表示weak指针之前是够指向了一个弱引用
- haveNew:bool类型,表示weak指针是否需要指向一个新指针
- crashIfDeallocating:bool类型,表示如果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash
- location:__weak指针的内存地址
- newObj:弱引用的对象
2.维护了两个SideTable类型的表:
- oldTable:旧的引用表
- newTable:新的引用表
3.对oldTable、newTable进行赋值处理
- 如果 weak指针 之前弱引用过一个 obj ,则将这个 obj 所对应的 SideTable 取出,赋值给oldTable,否则就将 oldTable 置为nil
- 如果 weak指针 要引用一个新的obj,就把 obj 对应的 sideTable 取出,赋值给 newTable ,否则将 newTable 置为nil。
4.对oldTable、newTable中的weak_entry_t进行维护
- 如果weak_ptr之前弱引用过别的对象oldObj,则调用
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location),在oldObj的weak_entry_t中移除该weak_ptr地址 - 如果weak指针需要指向一个新的引用,则会调用
weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating);方法将weak ptr的地址记录到newObj对应的weak_entry_t中
5.调用setWeaklyReferenced_nolock方法修改weak新引用的对象的bit标志位
// Template parameters.
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);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// 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:
if (haveOld) { // 如果weak ptr之前弱引用过一个obj,则将这个obj所对应的SideTable取出,赋值给oldTable
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil; // 如果weak ptr之前没有弱引用过一个obj,则oldTable = nil
}
if (haveNew) { // 如果weak ptr要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTable
newTable = &SideTables()[newObj];
} else {
newTable = nil; // 如果weak ptr不需要引用一个新obj,则newTable = nil
}
// 加锁操作,防止多线程中竞争冲突
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改
if (haveOld && *location != oldObj) {
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.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) // 如果cls还没有初始化,先初始化,再尝试设置weak
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(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; // 这里记录一下previouslyInitializedClass, 防止改if分支再次进入
goto retry; // 重新获取一遍newObj,这时的newObj应该已经初始化过了
}
}
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); // 如果weak_ptr之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址
}
// Assign new value, if any.
if (haveNew) { // 如果weak_ptr需要弱引用新的对象newObj
// (1) 调用weak_register_no_lock方法,将weak ptr的地址记录到newObj对应的weak_entry_t中
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// (2) 更新newObj的isa的weakly_referenced bit标志位
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
// (3)*location 赋值,也就是将weak ptr直接指向了newObj。可以看到,这里并没有将newObj的引用计数+1
*location = (id)newObj; // 将weak ptr指向object
}
else {
// No new value. The storage is not changed.
}
// 解锁,其他线程可以访问oldTable, newTable了
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj; // 返回newObj,此时的newObj与刚传入时相比,weakly-referenced bit位置1
}
SideTable
SideTable结构入戏
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
}
-
spinlock_t slock: 互斥锁,用于上锁、解锁sideTable
-
RefcountMap refcnts :用来存储OC对象的引用计数的hash表:
- 没开启isa优化之前,直接使用refcnts进行计数
- 开启isa优化之后,尽在 isa 共用体中的
extra_rc的存储计数溢出时才会向refcnts转移。每次转移一半
-
weak_table_t weakTable:存储对象弱引用指针的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类型元素。weak_entry_t就是弱引用对象的相关信息 - num_entries: hash数组中的元素个数
- mask:hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)
- max_hash_displacement:可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)
weak_entry_t
weak_entry_t 也是一个哈希表,存储的元素是指向弱引用对象指针的指针,通过操作指针的方式使得 weak指针在引用对象析构之后指向nil
引用该对象的对象列表,联合。 引用个数小于4,用inline_referrers数组。 用个数大于4,用动态数组weak_referrer_t *referrers
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];
};
};
stripeMap(SideTables)、sidetable、weak_table_t、wean_entry_t之间的关系
上述的这些都是哈希表结构
- 在
runtime中,通过SideTable来管理对象的引用计数以及weak的引用。一张 SideTable 会管理很多个对象,而非一个,而这一个个的SideTable又构成一个个集合,叫SideTables(其类型是template<typename T> class StripedMap,StripedMap<SideTable>)。一个stipeMap在iOS中有八个 sidetable, 在mac上有 64 个。
id object = [[NSObeject alloc] init];
id __weak obj = object;
-
stripMap:
- key :
__weak指针指向的地址,即上述代码段的object的地址 - value: 一张sideTable
- key :
-
sideTable:
- Key:
__weak指针指向的地址,即上述代码段的object的地址 - Value: weak_table_t
- Key:
-
weak_table_t:
- Key:
__weak指针指向的地址,即上述代码段的object的地址 - Value: weak_entry_t
- Key:
-
weak_entry_t :
- Key:
__weak指针的地址 - value:
__weak指针的地址
- Key:
总结
所有弱引用obj的指针地址都保存在obj对应的weak_entry_t中。当obj要析构时,会遍历weak_entry_t中保存的弱引用指针地址,并将弱引用指针指向nil,同时,将weak_entry_t移除出weak_table。
1.weak的原理在于维护了一张 weak_table_t 哈希表, key是引用对象的地址,value是weak指针的地址数组
2.weak 关键字的作用是弱引用,所引用对象的计数器不会加1,并在引用对象被释放的时候自动被设置为 nil。、
3.对象释放时,调用clearDeallocating函数根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。