开启生长之旅!这是我参加「日新计划 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目标的引证计数。