开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
一、storeWeak流程
先看一个例子代码:
@autoreleasepool {
NYPerson *p = [NYPerson new];
__weak typeof(p) weakP = p;
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(p)));
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(weakP)));
}
运行查看打印结果:
new出来的对象RetainCount是1,这个很好理解。但是__weak修饰的weakP为什么RetainCount是2呢?
那么__weak在底层到底做了什么?
我们通过断点调试,进入源码看看: 通过断点打印,得知objc_initWeak(id *location,id newObj)中的location代表weakP指针地址,newObj代表p对象NYPerson.
然后我们看到了真正的核心方法: storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object*)newObj);
- <DontHaveOld, DoHaveNew, DoCrashIfDeallocating> 代表模版参数。
- HaveOld 代表weak指针是否指向了一个弱引用
- HaveNew 代表weak指针是否需要只向一个新的弱引用
- CrashIfDeallocating 代表的是被弱引用的对象是否在析构,如果在析构会error。
查看storeWeak核心代码:
static id
storeWeak(id *location, objc_object *newObj)
{
//...........................省略.............................//
//获取两张表
SideTable *oldTable;//oldTable
SideTable *newTable;//newTable
retry:
if (haveOld) {
oldObj = *location;//拿到old被弱引用的对象
oldTable = &SideTables()[oldObj];//在通过这个对象获取oldTable-SideTable表
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];//在通过newObj获取newTable-SideTable表
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);//设置加锁
if (haveOld && *location != oldObj) {//haveOld 存在 并且 *location指针指向的老对象 和 oldObj不相同
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); //解锁
goto retry;//回到retry 再次判断
}
if (haveNew && newObj) {//新的弱引用指向 和 新对象
Class cls = newObj->getIsa();//拿到isa指向的元类
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) //判断类没有初始化
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);//解锁
class_initialize(cls, (id)newObj);//初始化 +1
//...........................省略.............................//
goto retry;//重新retry
}
}
if (haveOld) {//如果曾指向老的对象
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);//移除在老的weak_table的数据
}
if (haveNew) {//如果有一个新引用
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
//添加新的引用和对象到weak_table表中
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (!_objc_isTaggedPointerOrNil(newObj)) {//判断是否是TaggedPointer
newObj->setWeaklyReferenced_nolock();//设置newObj的weakly_referenced = true;是否被弱引用
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;//赋值到weakP指针指向的地址
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);//解锁
//...........................省略.............................//
return (id)newObj;
}
小结:
__weak底层执行的是storeWeak函数
,它的作用是根据location和newObj参数获取,oldTable和newTable然后判断,如果weak指针之前指向了一个弱引用,就会调用weak_unregister_no_lock
将weak指针地址移除。如果weak指针指向了一个新的弱引用,就会调用weak_register_no_lock
将weak指针地址添加到对象的弱引用表,通过setWeaklyReferenced_nolock
设置newisa.weakly_referenced
为 true;
二、weak原理
上篇文章已经了解了 weak_table_t 的结构及作用了。
这边在进行一些补充:
weak_entry_t *weak_entries;
是一个哈希数组,里面存储弱引用对象的相关信息。
然后我们在看weak_register_no_lock
函数:
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
//referent -- 被弱引用的对象
//referrer -- weak指针的地址
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
//判断是否TaggedPointer 是返回referent_id
if (_objc_isTaggedPointerOrNil(referent)) return referent_id;
// 确保弱引用对象是可用的。
if (deallocatingOptions == ReturnNilIfDeallocating ||
deallocatingOptions == CrashIfDeallocating) {
//...........................省略.............................//
}
weak_entry_t *entry;
//被弱引用的referent里面的weak_table中找到weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);// 往weak_entry_t里插入referrer -- weak指针的地址
}
else {
//没找到weak_entry_t就新建一张
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);//在把new_entry插入到weak_table中
}
return referent_id;
}
weak_register_no_lock
添加弱引用函数流程:
- 如果被弱引用的对象为nil或这是一个
TaggedPointer
,直接返回,不做任何操作。 - 如果被弱引用的对象正在析构,则抛出异常。
- 如果被弱引用的对象不能被weak引用,直接返回nil。
- 如果对象没有再析构并且可以被weak引用,则调用
weak_entry_for_referent
方法根据弱引用对象的地址从弱引用表中找到对应的weak_entry_t,如果能够找到则调用append_referrer
方法向其中插入weak指针地址。否则新建一个weak_entry_t。
我们在看weak_unregister_no_lock
函数:
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
//referent -- 被弱引用的对象
//referrer -- weak指针的地址
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
// 被弱引用的对象 ,不存在返回
if (!referent) return;
// 被弱引用的referent里面的weak_table中找到weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);//从weak_entry_t 中移除weak指针的地址
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;
}
}
}
// 4张表中都不存在referrer 指针的地址,并且entry 中已经weak指针已被移除
if (empty) {
weak_entry_remove(weak_table, entry);//移除weak_table中的weak_entry_t
}
}
}
weak_unregister_no_lock
移除弱引用函数流程:
referent
被弱引用的对象 ,不存在直接返回。- 通过
weak_entry_for_referent
方法在weak_table中找出被弱引用对象对应的weak_entry_t
。 - 在weak_entry_t中移除weak指针的地址。
- 移除元素后,判断此时
weak_entry_t
中是否还有元素,如果此时weak_entry_t
已经没有元素了,则需要将weak_entry_t
从weak_table中移除。
整理了一个对象sidetable,weak关系图:
三、weak引用计数问题
重新回到例子1的问题,但是__weak修饰的weakP为什么RetainCount是2呢?
我们开始断点调试,看看CFGetRetainCount弱引用和强引用有什么区别。
查看强引用汇编:
查看弱引用汇编:
看到weakP
在CFGetRetainCount
前会执行objc_loadWeakRetained
这个函数。
然后我们搜索objc_loadWeakRetained
这个函数进入源码:
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// fixme std::atomic this load
obj = *location;//获取弱引指针指向的对象
if (_objc_isTaggedPointerOrNil(obj)) return obj;//如果是TaggedPointer直接返回
table = &SideTables()[obj];//获取对象的SideTable
table->lock();//加锁
if (*location != obj) {//指针指指向的对象和obj不相等
table->unlock();//解锁
goto retry;
}
result = obj;
cls = obj->ISA();//得到对象的ISA
if (! cls->hasCustomRR()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
ASSERT(cls->isInitialized());
if (! obj->rootTryRetain()) {//rootTryRetain->rootRetain 这里加1了
result = nil;
}
}
else {
//...........................省略.............................//
}
table->unlock();
return result;//局部变量 在arc中会-1
}
继续断点控制台打印: 我们在看一个情况:
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(p)));
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(weakP)));
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(p)));
看打印结果:
为什么经过objc_loadWeakRetained
函数然后在源码找到了obj->rootTryRetain()
所以obj引用计数+1了,源p的引用计数还是计数1呢?
因为在objc_loadWeakRetained
函数中result是局部变量在arc中,执行完了会在减1.所以影响不到外面的p对象的引用计数。
总结
storeWeak
:__weak底层执行的是storeWeak函数
,它的作用是根据location和newObj参数获取,oldTable和newTable然后判断(都是sideTable类型)。- 如果weak指针之前指向了一个弱引用,就会调用
weak_unregister_no_lock
将weak指针地址移除。 - 如果weak指针指向了一个新的弱引用,就会调用
weak_register_no_lock
将weak指针地址添加到对象的弱引用表
- 如果weak指针之前指向了一个弱引用,就会调用
weak原理
:就是通过对象的sideTable找到相关的weaktable表在通过weak_unregister_no_lock
,weak_register_no_lock
两个函数添加和移除weak指针地址。weak_unregister_no_lock
:如果对象没有再析构并且可以被weak引用,则调用weak_entry_for_referent
方法根据弱引用对象的地址从弱引用表中找到对应的weak_entry_t,如果能够找到则调用append_referrer
方法向其中插入weak指针地址。否则新建一个weak_entry_t。weak_register_no_lock
:通过weak_entry_for_referent
方法在weak_table中找出被弱引用对象对应的weak_entry_t
,在weak_entry_t中移除weak指针的地址。
weak引用计数问题
:在打印CFGetRetainCount((__bridge CFTypeRef)(weakP))
之前执行了objc_loadWeakRetained
函数里面有个rootTryRetain()->rootRetain()
然后引用计数就+1了。并且不影响外部的p对象的引用计数。