本文使用的 runtime 版本為 objc4-706筑辨。
retain
retain
在現(xiàn)在的 runtime 中的默認實現(xiàn)是 objc_object
中的 retain
函數(shù)获黔,可以在 objc-object.h
中找到它:
// Equivalent to calling [this retain], with shortcuts if there is no override
inline id
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
retain
函數(shù)首先斷言對象指針不是一個 tagged pointer(assert(!isTaggedPointer())
)簿训,之后對 isa
中是否有自定義 retain
和 release
實現(xiàn)標示位進行判斷骇径,如果沒有自定義的實現(xiàn),則進入默認實現(xiàn) rootRetain
函數(shù)缭贡,否則的話直接向?qū)ο蟀l(fā)送 retain
消息炉擅,調(diào)用自定義的 retain
實現(xiàn)辉懒。
本文的關(guān)注點當(dāng)然是在默認實現(xiàn)上,所以繼續(xù)查看 rootRetain
函數(shù)的實現(xiàn):
ALWAYS_INLINE id
objc_object::rootRetain()
{
return rootRetain(false, false);
}
rootRetain
函數(shù)的實現(xiàn)是調(diào)用了另一個重載的 rootRetain
谍失。
在繼續(xù)對下面的代碼進行分析之前眶俩,先回顧一下 isa
的結(jié)構(gòu)(這里只對 x86-64 架構(gòu)的 isa_t
進行分析):
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
};
在《Objective-C 小記(2)對象 2.0》中有對 isa_t
更詳細的描述。現(xiàn)在需要關(guān)心的是 has_sidetable_rc
和 extra_rc
這兩個位字段(bit-field)快鱼。extra_rc
表示「額外的 retain count」颠印,假如 extra_rc
的值為 2,則對象的引用計數(shù)為 3抹竹∠吆保回顧《Objective-C 小記(6)alloc & init》可以發(fā)現(xiàn),對象在創(chuàng)建時 extra_rc
的值是 0窃判,引用計數(shù)則是 1钞楼。還可以注意到 extra_rc
只有 8 位,這樣它最多能記到 255袄琳,如果這個時候引用計數(shù)還要往上增加怎么辦呢询件?這時候?qū)ο髸⒁话氲囊糜嫈?shù)存儲到一個表里,并將 has_sidetable_rc
置為 1跨蟹。
回到 rootRetain
函數(shù):
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
函數(shù)一開始又檢查了自己是不是 tagged pointer(if (isTaggedPointer()) return (id)this;
)雳殊,這難道就是防御式編程?
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
它首先聲明了四個變量窗轩,四個變量都能從名字知道它們的用處:
-
sideTableLocked
,用來表示 side table 是否鎖上了 -
transcribeToSideTable
座咆,用來表示是否需要將isa
中的引用計數(shù)轉(zhuǎn)移到 side table 里去 -
oldisa
痢艺,isa
本來的值 -
newisa
,isa
新的值(增加了引用計數(shù)后的值)
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
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;
}
之后進入 do-while 循環(huán)介陶,循環(huán)里首先將 transcribeToSideTable
賦值為 false
堤舒,oldisa
和 newisa
賦值為 isa.bits
的值(LoadExclusive
的作用是讓讀取操作原子化,根據(jù) CPU 不同實現(xiàn)不同哺呜,比如在 x86-64 上就是單純的直接返回值舌缤,而在 arm64 上則使用了 ldxr
指令)。
關(guān)于
slowpath
和fastpath
宏某残,在《Objective-C 小記(6)alloc & init》中有解釋国撵。關(guān)于
tryRetain
,這個參數(shù)與 weak 的實現(xiàn)有關(guān)玻墅,本文暫不做分析介牙。
首先會檢查 isa
是不是 non-pointer(if (slowpath(!newisa.nonpointer)) { ... }
),如果不是 non-pointer澳厢,就進入 sidetable_retain
這個過程环础,這是完全由一個表來存放引用計數(shù)的實現(xiàn)囚似。
第二個判斷則是和 tryRetain
有關(guān),暫時不做分析线得∪幕剑可以發(fā)現(xiàn)這兩個判斷使用的都是 slowpath
,表示是不太可能出現(xiàn)的情況贯钩。
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// 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;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
接下來就是重點部分了募狂,聲明 carry
變量來標示是否溢出。然后使用 addc(newisa.bits, RC_ONE, 0, &carry)
給 newisa
的 extra_rc
位字段加 1魏保。這里有個判斷是否溢出熬尺,如果溢出的話還要判斷 handleOverflow
是否為 true
,可以注意到這個函數(shù)被調(diào)用時 hadleOverflow
是 false
谓罗,需要進入 rootRetain_overflow
函數(shù)粱哼,而 rootRetain_overflow
的實現(xiàn)是這樣的:
NEVER_INLINE id
objc_object::rootRetain_overflow(bool tryRetain)
{
return rootRetain(tryRetain, true);
}
它又重新調(diào)用 rootRetain
,不過將 handleOverflow
置為了 true
檩咱,希望有大神分享一下為什么要這樣做……rootRetain
里剩余的工作也很好理解揭措,將 side table 鎖住,給 sideTableLocked
和 transcribeToSideTable
設(shè)置好值刻蚯,extra_rc
留下一半(在 x86-64 下就是 126)的引用計數(shù)绊含,并將 has_sidetable_rc
設(shè)置為 true
。
最后 while
里的操作是對比 isa
和 oldisa
的值炊汹,如果一樣則將 newisa
覆蓋 isa
躬充,否則需要重新操作。
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
最后讨便,函數(shù)檢查 transcribeToSideTable
充甚,也就是如果之前的操作有溢出,則將一半的引用計數(shù)加到表里霸褒。
release
release
的實現(xiàn)也在 objc-object.h
中:
// Equivalent to calling [this release], with shortcuts if there is no override
inline void
objc_object::release()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
rootRelease();
return;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}
和 retain
基本上是一致的伴找,如果有自定義實現(xiàn)的話,則發(fā)消息废菱,否則進入默認實現(xiàn) rootRelease
:
ALWAYS_INLINE bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
套路真是一模一樣技矮,繼續(xù)看 rootRelease
的實現(xiàn):
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
開頭也是一樣的套路,不解釋了殊轴。
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
同樣也是進入一個 do-while 循環(huán)衰倦,套路滿滿,這里也不解釋了梳凛。
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
使用 subc(newisa.bits, RC_ONE, 0, &carry)
給 newisa
的引用計數(shù)減 1耿币,發(fā)現(xiàn)下溢出后跳轉(zhuǎn)到 underflow
。如果沒有溢出韧拒,函數(shù)就這樣結(jié)束了淹接。繼續(xù)看 underflow
的代碼:
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
首先將 newisa
重制十性,然后判斷這個對象有沒有 side table,有的話塑悼,可以把 side table 里的引用計數(shù)移過來劲适。但判斷里面又是判斷 handleUnderflow
這個參數(shù),rootRelease_underflow
的實現(xiàn)也是和 rootRetain_overflow
差不多的:
NEVER_INLINE bool
objc_object::rootRelease_underflow(bool performDealloc)
{
return rootRelease(performDealloc, true);
}
總之調(diào)用了這個函數(shù)還是會回到上面的代碼厢蒜,就繼續(xù)往下看吧:
// Transfer retain count from side table to inline storage.
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
goto retry;
}
首先將 side table 鎖住霞势,為了防止出現(xiàn)競爭又跑一遍 retry
。
// Try to remove some retain counts from the side table.
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
if (borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
這里從 side table 借 RC_HALF
的引用計數(shù)放到 extra_rc
上斑鸦。接下來的代碼是從 side table 借不到的情況愕贡,那當(dāng)然就是對象需要被銷毀了。
// Really deallocate.
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__sync_synchronize();
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}
可以看到巷屿,就是直接就發(fā)送了 dealloc
消息固以。
總結(jié)
對于現(xiàn)在的 non-pointer isa 來說,引用計數(shù)一部分存儲在 isa 的 extra_rc
上嘱巾,溢出后轉(zhuǎn)移到一個表里憨琳。感覺是個很有意思的實現(xiàn)。