了解了release的底层实现之后,再来看看retain的内部实现
1. 内部调用流程
_objc_rootRetain
- (id)retain {
return _objc_rootRetain(self);
}
rootRetain
NEVER_INLINE id
_objc_rootRetain(id obj)
{
ASSERT(obj);
return obj->rootRetain();
}
objc_object::rootRetain()
需要注意的是跟release一样,系统会对是否支持SUPPORT_NONPOINTER_ISA
做不同的处理
- 支持Nonpointer isa的处理
ALWAYS_INLINE id
objc_object::rootRetain()
{
return rootRetain(false, false);
}
- 不支持Nonpointer isa的处理
inline id
objc_object::rootRetain()
{
if (isTaggedPointer()) return (id)this;
return sidetable_retain();
}
可以看到不支持Nonpointer isa的处理就是直接sidetable_retain,这是由于计数都存储在sidetable中了,处理逻辑较支持Nonpointer isa的情况要简单一些;接下来分别看看内部实现
2. 内部实现
2.1 不支持Nonpointer isa的处理
不支持Nonpointer isa的架构一般就是32bit,isa指针就是纯粹的指针,而64bit的isa,一些位存储了额外的标记信息以及计数
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
逻辑也比较清晰,去sidetable取出计数信息,执行+1操作
2.2 支持Nonpointer isa的处理
由于Nonpointer isa,isa本身包含了计数信息,同时当计数超过isa的计数位能表示的范围,就会存储一部分到sidetable中去,所以处理的逻辑会复杂一点
在objc-object.h
中的objc_object::rootRetain(bool tryRetain, bool handleOverflow)
方法实现如下
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow) // retain的传参数 false、false
{
if (isTaggedPointer()) return (id)this; // tagged pointer直接返回
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) { // 支持Nonpointer isa的架构,但是isa没有额外信息
// 引用计数存储在sidetable中,走sidetable的流程
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
// Nonpointer isa中存储有额外信息,包含引用计数
uintptr_t carry; // 是否溢出的标记,1则表示溢出了
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) { // 超过extra_rc所能表示的范围了
// newisa.extra_rc++ overflowed
if (!handleOverflow) { // retain的实现handleOverflow传的是false,当溢出了会执行下面这个分支
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain); // 这里调用的也是该函数,只是handleOverflow传的是true,会走下面的流程
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true; // 标记需要将计数迁移到sidetable中去
newisa.extra_rc = RC_HALF; // 将extra_rc设置为它可以表示的最大数的一半,另外一半则存储到sidetable中去
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF); // 将另外一半的计数存储到sidetable中去
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
整体流程
创建一个临时的isa:newisa,操作都是针对临时变量来操作,最后将newisa的bits信息存储到原始的isa中去,有个do-while去处理存储的逻辑,失败的话就重试
- <1> 对于Nonpointer isa没有存储额外信息的,走sidetable_retain将sidetable中计数+1的流程
- <2> 对于Nonpointer isa中存储额外信息的情况,尝试对isa中的extra_rc++
- <2.1> extra_rc++没有溢出的情况,则将isa的值修改为extra_rc++之后的值
- <2.2> extra_rc++溢出的情况,则处理overflow流程,将一半的计数存储到extra_rc,另一半存储到sidetable中去,同时设置标记位has_sidetable_rc为true
3.总结
- 对于64bit的系统isa可以存储额外的信息,那么有extra_rc位用来存储引用计数;当超过它所表示的范围,就会存储计数到sidetable中
- retain的流程也是针对isa是否是Nonpointer isa分别做了处理
- 个人觉得巧妙的一点就是:当extra_rc存储溢出了,这个时候是一半(extra_rc能表示的
最大值+1
的一半)在extra_rc一半存储在sidetable中,这里跟release的操作是对应的(extra_rc不够减了也是去sidetable借extra_rc最大值的一半的计数),这样设计的好处避免了频繁的去sidetable中读取计数信息---假如我们溢出了把计数全部存到sidetable中去,那么有release的时候,extra_rc也不够减了,又去借,这就大大降低了效率,比起直接操作isa。
网友评论