八、弱应用(Weak)的实现
2018-06-27 本文已影响0人
LNG61
一、弱引用Weak初始化
我们直接在main函数加上以下代码:
NSObject *object1 = [NSObject new];
NSObject *object2 = [NSObject new];
__weak NSObject *weakRef = object1;
weakRef = object2;
object2= nil;
图一
如上图所示,在
__weak NSObject *weakRef = object1;
语句之后,step into
执行的下一个函数为objc_initWeak
,通过这个命名我们可以猜测是用来初始化弱应用的。
id objc_initWeak(id *location, id newObj){
if(!newObj){
// 赋值为nil
*location = nil;
return nil;
}
return storeWeak<false, true, true>(location, (objc_object *)newObj);
}
objc_initWeak
该方法主要是storeWeak
的一个入口,接下来看下storeWeak
是怎样实现的。
template <bool haveOld, bool haveNew, bool crashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj){
id oldObj;
SideTable *oldTable, *newTable;
retry:
if(haveOld){
// 如果有旧对象,则获取
oldObj = *location;
oldTable = &SideTables()[oldObj];
}else{
oldTable = nil;
}
if(haveNew){
newTable = &SideTables()[newObj];
}else{
newTable = nil;
}
// 其它逻辑处理
if(haveOld){
// 清理旧对象
weak_unregister_no_lock(&oldTable->weakTable, oldObj, location);
}
if(haveNew){
// 赋新值
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating);
newObj->setWeaklyReferenced_nolock();
*location = (id)newObj;
}
return (id)newObj;
}
storeWeak
的主要逻辑是将获取到oldObj和newObj
对应的弱引用表,然后在旧表中移除旧值,在新表中添加新值。这里涉及到SideTables()
,其主要作用是返回一个全局的哈希表StripedMap<SideTable>
,以对象的哈希值作为索引。
SideTable
的结构:
struct SideTable{
spinlock_t slock; // 锁
RefcountMap refcnts; // 引用计数
weak_table_t weak_table; // 弱引用表
}
struct weak_table_t{
weak_entry_t *weak_entries; //弱引用登记
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
}
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{
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; //优化后的
}
}
}
每个被弱引用的对象对应一个weak_entry_t
结构,然后在referrers
或inline_referrers
中保存这所有弱引用该对象的对象地址。
二、弱引用注册和移除
方法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 = weak_entry_for_referent(weak_table, referent);
if(entry){
remove_referrer(entry, referrer);
if(empty){
// 如果referrer没有被弱引用了,则移除弱引用表
weak_entry_remove(weak_table, entry);
}
}
}
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;
// 处理是否dealloc的逻辑
// 插入table中
weak_entry *entry = weak_entry_for_referent(weak_table, referent);
if(entry){
// entry 已经存在
append_referrer(entry, referrer);
}else{
// 生成新entry
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
三、弱引用置为nil
我们知道,当弱引用的对象被释放时,所有指向该对象的弱引用都会被置为nil
,接下来我们来看下这个是如何实现的。在最开始的例子中,后面有这样一段代码:
weakRef = object2;
object2 = nil;
同样我们通过断点的方式看下这过程是如何调用的,如下图:
经过一系列的调用之后,最终是调用
weak_clear_no_lock
来清理该对象的弱引用。weak_clear_no_lock
的部分实现:
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);
for(size_t i = 0; i < WEAK_LINE_COUNT; ++i){
object **referrer = referrers[i];
if(*referrer == referent){
// 如果该对象是指向referent_id,则置为nil
*referrer = nil;
}
}
weak_entry_remove(weak_table, entry);
}
在上面的weak_clear_no_lock
中,referent
就是object2
,然后遍历object2
所有的弱引用,置为nil
,这样就实现了对象释放后,所以指向该对象的弱引用自动变为nil
的过程。
小结
- 弱引用是一个全局哈希表,每个对象对应一个
weak_entry_t
,weak_entry_t
下面保存了所有指向该对象的弱引用指针。 - 对象释放后,会调用
weak_clear_no_lock
方法将所有指向该对象的弱引用指针赋值为nil。