-
ARC
是LLVM
和Runtime
配合的結(jié)果。 -
ARC
中禁止手動調(diào)用retain
/release
/retainCount
/dealloc
-
ARC
新加了weak
凰棉、strong
屬性關(guān)鍵字
一损拢、 retain 源碼解析
1.1 rootRetain 核心源碼
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
//TaggedPointer 直接返回
if (slowpath(isTaggedPointer())) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
oldisa = LoadExclusive(&isa.bits);
......
do {
transcribeToSideTable = false;
newisa = oldisa;
//純指針
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
//散列表引用計數(shù) + 1
else return sidetable_retain(sideTableLocked);
}
// don't check newisa.fast_rr; we already called any RR overrides
//非純指針 nonpointer
//正在釋放(為了多線程)不做處理。
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil;
} else {
return (id)this;
}
}
//
uintptr_t carry;
//newisa.bits + 1, RC_ONE 從 extra_rc 的最低位開始+1撒犀。相當(dāng)于extra_rc + 1福压。加滿了標(biāo)記 carry
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
//所有超載
if (variant != RRVariant::Full) {
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;
//標(biāo)記散列表存儲
transcribeToSideTable = true;
//extra_rc 減半
newisa.extra_rc = RC_HALF;
//標(biāo)記有散列表
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
//isa extre_rc滿了
if (variant == RRVariant::Full) {
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
//散列表引用計數(shù) + 一半
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else {
......
}
return (id)this;
}
- 判斷是否
TaggedPointer
掏秩,TaggedPointer
直接返回。 - 非
nonpointer
散列表引用計數(shù)+1
荆姆。 - 對象正在釋放不進(jìn)行操作蒙幻。
-
nonpointer
則extra_rc + 1
。-
extra_rc
超載的情況下has_sidetable_rc
設(shè)置為true
胆筒。 -
extra_rc
減半邮破。 - 散列表加一半
extra_rc
。
-
1.2 sidetable_retain
id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
//獲取對象對應(yīng)的散列表 SideTable
SideTable& table = SideTables()[this];
if (!locked) table.lock();
size_t& refcntStorage = table.refcnts[this];
//引用計數(shù)+1
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
//1 << 2, +2 是在 refcntStorage 上 +2腐泻,因為引用計數(shù)存儲在 SideTable 的 refcntStorage 位置從1開始决乎,不是從0開始。
//這里+2 相當(dāng)于+ 0b010派桩,只對1號位置加
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
- 獲取對象對應(yīng)的
SideTable
。 - 獲取
SideTable
中的引用計數(shù)表refcnts
蚌斩。 - 從引用計數(shù)表中找到
refcntStorage
铆惑。 -
refcntStorage +2
引用計數(shù)+1
,這里+2
是因為加到對應(yīng)的位上送膳,從1
開始员魏。
1.3 sidetable_addExtraRC_nolock
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this];
//從SideTable對應(yīng)的引用計數(shù)表中取出對象的引用計數(shù)refcntStorage
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be set here
ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
uintptr_t carry;
//散列表引用計數(shù) + extra_rc的一半。從1號位置開始存叠聋,所以需要 delta_rc << 2
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
if (carry) {
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {
refcntStorage = newRefcnt;
return false;
}
}
- 獲取對象對應(yīng)的
SideTable
撕阎。 - 獲取
SideTable
中的引用計數(shù)表refcnts
。 - 從引用計數(shù)表中找到
refcntStorage
碌补。 -
refcntStorage + delta_rc << SIDE_TABLE_RC_SHIF
引用計數(shù)+ extra_rc
最大值的一半虏束。
散列表和extra_rc
各存儲一半是因為extra_rc
可以通過isa
直接拿到而散列表需要去查找表然后找到對象的引用計數(shù)區(qū)域。散列表還有加解鎖厦章。在extra_rc
中操作方便快速镇匀。extra_rc
每次存儲挪一半是為了避免在retain
和release
頻繁操作的時候而導(dǎo)致散列表頻繁操作。
1.4 retain 流程
-
TaggedpPointer
直接返回袜啃。 - 非
nonpointer isa
引用計數(shù)表引用計數(shù)+1
。 -
nonpointer isa
extra_rc +1
群发。- 如果
extra_rc
上溢出(iOS
真機255
)晰韵,extra_rc
值減半(128
)。 -
extra_rc
減少一半的值存入引用計數(shù)表熟妓。
- 如果
二雪猪、 release 源碼解析
2.1 release 核心源碼
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
//判斷是否TaggedPointer,TaggedPointer直接返回
if (slowpath(isTaggedPointer())) return false;
bool sideTableLocked = false;
isa_t newisa, oldisa;
......
retry:
do {
newisa = oldisa;
//非 nonpointer
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
//散列表 引用計數(shù)-1
return sidetable_release(sideTableLocked, performDealloc);
}
//在釋放 返回
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
return false;
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
//extra_rc - 1滑蚯。減一后如果extra_rc=0了浪蹂,則標(biāo)記carry抵栈。
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {//跳轉(zhuǎn)underflow
// don't ClearExclusive()
// printf("newisa.extra_rc: %d\n",newisa.extra_rc);
//存儲滿的情況下
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
if (slowpath(newisa.isDeallocating()))
goto deallocate;
if (variant == RRVariant::Full) {
if (slowpath(sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!sideTableLocked);
}
return false;
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 (variant != RRVariant::Full) {
//清空extra_rc
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// 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.
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
// Try to remove some retain counts from the side table.
//將sidetable中取一半(extra_rc 最大值的一半)存到 extra_rc 中。borrow為借過來的值坤次。
auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
//散列表中沒有值則標(biāo)記清空散列表
bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there
if (borrow.borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
bool didTransitionToDeallocating = false;
//extra_rc 值為 borrowed - 1古劲。extra_rc 發(fā)生溢出了所以-1存儲
newisa.extra_rc = borrow.borrowed - 1; // redo the original decrement too
// printf("newisa.extra_rc111 : %d\n",newisa.extra_rc);
newisa.has_sidetable_rc = !emptySideTable;
//存儲
bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
//存儲失敗則重新存
if (!stored && oldisa.nonpointer) {
// 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.
uintptr_t overflow;
//借過來的存入到bits。
newisa.bits =
addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
// printf("newisa.extra_rc222 : %d\n",newisa.extra_rc);
newisa.has_sidetable_rc = !emptySideTable;
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
if (stored) {
didTransitionToDeallocating = newisa.isDeallocating();
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
ClearExclusive(&isa.bits);
//沒有存儲成功則 sidetable 加回去
sidetable_addExtraRC_nolock(borrow.borrowed);
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
// Decrement successful after borrowing from side table.
//清空sidetable
if (emptySideTable)
sidetable_clearExtraRC_nolock();
if (!didTransitionToDeallocating) {
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
}
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
//沒有散列表直接釋放
deallocate:
// Really deallocate.
ASSERT(newisa.isDeallocating());
ASSERT(isa.isDeallocating());
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
//調(diào)用dealloc
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
- 判斷是否
TaggedPointer
缰猴,TaggedPointer
直接返回产艾。 - 非
nonpointer isa
則直接散列表引用計數(shù)-1
。 - 如果對象在釋放直接返回
false
滑绒。 -
nonpointer isa
則extra_rc - 1
闷堡。 -
extra_rc
溢出的情況判斷has_sidetable_rc
。 -
has_sidetable_rc
為true
的情況則sidetable
減去extra_rc
最大值的一半疑故,值存儲到borrow
杠览。 -
extra_rc
設(shè)置為borrow.borrowed - 1
(溢出了要減去1
再存儲,相當(dāng)于這次的release
)纵势。 -
borrow.remaining == 0
的情況則設(shè)置emptySideTable
清空對象對應(yīng)的SideTable
踱阿。 -
extra_rc
為0
的情況則調(diào)用發(fā)送dealloc
消息。
2.2 sidetable_release
uintptr_t
objc_object::sidetable_release(bool locked, bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
//獲取對象的 SideTable
SideTable& table = SideTables()[this];
bool do_dealloc = false;
if (!locked) table.lock();
//獲取table中對象對象的引用計數(shù) refcnts
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
auto &refcnt = it.first->second;
if (it.second) {
do_dealloc = true;
} else if (refcnt < SIDE_TABLE_DEALLOCATING) {//在釋放
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
refcnt |= SIDE_TABLE_DEALLOCATING;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
//引用計數(shù)-1
refcnt -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return do_dealloc;
}
- 通過對象獲取
SideTable
的refcnts
钦铁。 -
refcnts - 2
相當(dāng)于引用計數(shù)-1
软舌。 - 如果引用計數(shù)為
0
則發(fā)送dealloc
消息。
2.3 sidetable_subExtraRC_nolock
objc_object::SidetableBorrow
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end() || it->second == 0) {
// Side table retain count is zero. Can't borrow.
return { 0, 0 };
}
size_t oldRefcnt = it->second;
// isa-side bits should not be set here
ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
//sidetable 減少 extra_rc 的最大值的一半(這里有位運算平移)
size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
ASSERT(oldRefcnt > newRefcnt); // shouldn't underflow
it->second = newRefcnt;
return { delta_rc, newRefcnt >> SIDE_TABLE_RC_SHIFT };
}
- 通過對象獲取
SideTable
的refcnts
牛曹。 -
newRefcnt
為oldRefcnt
減去extra_rc
一半佛点。 - 返回
delta_rc
以及剩余的newRefcnt
。
2.4 release流程
-
TaggedpPointer
直接返回false
黎比。 - 非
nonpointer isa
引用計數(shù)表引用計數(shù)-1
超营。如果引用計數(shù)為0
則調(diào)用dealloc
返回true
,否則返回false
焰手。 -
nonpointer isa
extra_rc -1
糟描。- 如果
extra_rc
下溢出 ,判斷has_sidetable_rc
书妻。- 沒有引用計數(shù)表則調(diào)用
dealloc
船响,返回true
。 - 有引用計數(shù)表則減去
extra_rc
最大值的一半(128
)存入extra_rc
散列表中如果沒有值了則清空散列表躲履,返回false
见间。
- 沒有引用計數(shù)表則調(diào)用
- 如果
extra_rc != 0
,返回false
工猜。
- 如果
總結(jié):
retain
針對相應(yīng)引用計數(shù)位+1
米诉,開啟nonpointer
的情況下,如果引用計數(shù)出現(xiàn)上溢出篷帅,那么開始分開存儲史侣,一半存到散列表拴泌。
release
針對相應(yīng)引用計數(shù)位-1
,開始nonpointer
的情況下惊橱,如果引用計數(shù)出現(xiàn)下溢出蚪腐,去散列表借來的引用計數(shù) -1
存到extra_rc
,依然下溢出則調(diào)用dealloc
税朴。
三回季、散列表(SideTable)
在retain
和release
的源碼中SideTable
是通過SideTables
獲取的:
SideTable& table = SideTables()[this];
那么證明SideTable
是有多張的,SideTables
的定義如下:
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
......
}
iPhone
真機下SideTables
中有8
張SideTable
正林,其它則為64
張泡一。
SideTable
對應(yīng)的結(jié)構(gòu):
struct SideTable {
spinlock_t slock;//os_unfair_lock
RefcountMap refcnts;//引用計數(shù)表
weak_table_t weak_table;//弱引用表
......
}
散列表中存儲了鎖、引用計數(shù)表觅廓、弱引用表鼻忠。
那么為什么使用多張表呢?
由于SideTable
有加鎖和解鎖杈绸,如果在整個系統(tǒng)中如果共用一張表那么就會有性能消耗(互斥)粥烁。多張表內(nèi)存可以置空回收。不是每個對象開辟一張表為了效率和性能蝇棉。
SideTables
結(jié)構(gòu)圖下:
3.1 引用計數(shù)(retainCount)
inline uintptr_t
objc_object::rootRetainCount()
{
//TaggedPointer 對象指針強轉(zhuǎn)返回。
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
//nonpointer
if (bits.nonpointer) {
//extra_rc芥永,之前的版本為 extra_rc + 1篡殷。由于之前的版本 alloc 的時候 extra_rc 不進(jìn)行 +1。目前版本 alloc 的時候進(jìn)行了賦值 1埋涧。
uintptr_t rc = bits.extra_rc;
if (bits.has_sidetable_rc) {
// extra_rc + 散列表引用計數(shù)
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
//非 nonpointer 獲取散列表中引用計數(shù)板辽。
return sidetable_retainCount();
}
-
TaggedPointer
直接返回對象的地址強轉(zhuǎn)為unsigned long
。 -
nonpointer
返回extra_rc + 引用計數(shù)表中引用計數(shù)
棘催。extra_rc
這里直接返回沒有+1
劲弦,之前的版本會有+1
操作。alloc
之前不會對extra_rc
賦值為1
醇坝,現(xiàn)在版本會賦值為1
邑跪。 - 非
nonpointer
直接返回引用計數(shù)表中引用計數(shù)。
alloc
的過程中在進(jìn)行initIsa
的時候?qū)?code>extra_rc進(jìn)行了賦值:
SideTable
數(shù)據(jù)內(nèi)容如下:
(lldb) p table
(SideTable) $5 = {
slock = {
mLock = (_os_unfair_lock_opaque = 775)
}
refcnts = {
Buckets = 0x0000000102b04080
NumEntries = 1
NumTombstones = 0
NumBuckets = 4
}
weak_table = {
weak_entries = 0x0000000000000000
num_entries = 0
mask = 0
max_hash_displacement = 0
}
}
refcnts
中存儲了引用計數(shù)的Buckets
呼猪,其中存儲了DisguisedPtr<objc_object>
(包裝了引用計數(shù))画畅,與關(guān)聯(lián)對象的存儲有些類似。
3.2 弱引用
有如下代碼:
NSObject *obj = [NSObject alloc];
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj);//1
__weak typeof(id) weakObjc = obj;
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc);//2
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj);//1
運行輸出:
1 - <NSObject: 0x101622330>
2 - <NSObject: 0x101622330>
1 - <NSObject: 0x101622330>
按照正常理解weak
不增加引用計數(shù)宋距,obj
輸出1
沒問題轴踱。weakObjc
的引用計數(shù)為什么輸出2
?
3.2.1 弱引用表
要了解weak
的引用計數(shù)首先要清楚weak
表的存儲邏輯谚赎。通過匯編跟蹤發(fā)現(xiàn)__weak
修飾的變量會進(jìn)入objc_initWeak
:
那么__weak
是怎么與objc_initWeak
關(guān)聯(lián)起來的呢淫僻?
在llvm
中有相關(guān)的映射诱篷,weak
和__weak
最終映射到了objc_initWeak
:
弱引用的存儲與釋放:
id
objc_initWeak(id *location, id newObj)
{
//對象不存在直接返回。
if (!newObj) {
//weak 指針置為 nil
*location = nil;
return nil;
}
//執(zhí)行存儲操作
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
void
objc_destroyWeak(id *location)
{
//與init同一個函數(shù)雳灵。傳遞 newObj 為 nil
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
最終都會調(diào)用同一個函數(shù)storeWeak
棕所。
3.2.1.1 storeWeak
//c++ 模板參數(shù)
template <HaveOld haveOld, HaveNew haveNew,
enum CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
//舊表
SideTable *oldTable;
//新表
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
//整體是SideTables
if (haveOld) {//是否有舊值,第一次進(jìn)來沒有细办。
oldObj = *location;
//取舊表
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
//取weak指針對應(yīng)的新表地址
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
//鎖定兩張表
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
......
// Clean up old value, if any.
if (haveOld) {//釋放
//清空舊值
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
newObj = (objc_object *)
//存儲新值
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (!newObj->isTaggedPointerOrNil()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
}
- 獲取
oldTable
與newTable
橙凳,newTable
用來存儲,oldTable
用來釋放笑撞。 - 通過
SideTables
獲取obj
對應(yīng)的SideTable
岛啸。 - 如果是釋放調(diào)用
weak_unregister_no_lock
釋放弱引用指針。參數(shù)傳遞SideTable
的weak_table
以及obj
和弱引用指針茴肥。 - 如果是存儲調(diào)用
weak_register_no_lock
存儲弱引用指針坚踩。參數(shù)傳遞SideTable
的weak_table
以及obj
和弱引用指針。
3.2.1.2 weak_unregister_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;
if (!referent) return;
//找到對象的 weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//將弱引用指針從weak_entry_t中移除瓤狐。
remove_referrer(entry, referrer);
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;
}
}
}
if (empty) {
//如果entry為空了瞬铸,則將entry從整個weak_table中移除
weak_entry_remove(weak_table, entry);
}
}
}
- 在
weak_table
中找到對象對應(yīng)的weak_entry_t
。 - 調(diào)用
remove_referrer
遍歷weak_entry_t
的inline_referrers
或者referrers
將對應(yīng)index
位置的值置為nil
础锐,并且num_refs - 1
嗓节。 - 如果
weak_entry_t
中已經(jīng)沒有值了,則調(diào)用weak_entry_remove
將entry
從weak_table
中清除并且釋放空間皆警。
3.2.1.3 weak_register_no_lock
//對象對應(yīng)的全局弱引用表拦宣,對象指針,弱引用指針
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
//對象
objc_object *referent = (objc_object *)referent_id;
//弱引用指針
objc_object **referrer = (objc_object **)referrer_id;
......
// now remember it and where it is being stored
weak_entry_t *entry;
//根據(jù)弱引用對象從weak_table中找出weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//將弱引用指針加入entry
append_referrer(entry, referrer);
}
else {
//通過弱引用指針與對象創(chuàng)建new_entry
weak_entry_t new_entry(referent, referrer);
//weak_table擴容
weak_grow_maybe(weak_table);
//將new_entry插入weak_table
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
//返回對象
return referent_id;
}
- 在
weak_table
中找到對象對應(yīng)的weak_entry_t
信姓。 - 找到對應(yīng)的
weak_entry_t
調(diào)用append_referrer
將弱引用指針加入weak_entry_t
(因為釋放過程中有置空操作鸵隧,所以找空位nil
加入,這個過程中可能會進(jìn)行擴容)意推。 - 找不到則根據(jù)對象和弱引用指針創(chuàng)建
weak_entry_t
豆瘫。- 調(diào)用
weak_grow_maybe
嘗試擴容。 - 調(diào)用
weak_entry_insert
將創(chuàng)建的weak_entry_t
加入weak_table
菊值。
- 調(diào)用
散列表完整結(jié)構(gòu):
3.2.2 weak 的引用計數(shù)
到目前為止仍然解釋不了為什么之前的案例弱引用計數(shù)為2
外驱,修改代碼如下:
NSObject *obj = [NSObject alloc];
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//1
__weak typeof(id) weakObjc = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//1
NSObject *obj2 = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj2)),obj2,&obj2);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);//3
__weak typeof(id) weakObjc2 = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);//3
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc2)),weakObjc2,&weakObjc2);//3
輸出:
1 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f8
1 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f0
3 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f8
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
3 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f8
3 - <NSObject: 0x101a1a730> - 0x7ffeefbff4e8
obj
引用計數(shù)為2
很好理解,weakObjc
多次指向也只增加了一次俊性。weakObjc
的引用計數(shù)看著是對象的引用計數(shù)+weak
的1
次略步。
CFGetRetainCount
調(diào)用的是retainCount
,那么顯然獲取的是obj
的引用計數(shù)定页,那么多的1
肯定做了額外的處理趟薄。
有如下代碼,對NSLog
打斷點:
NSObject *obj = [NSObject alloc];
__weak typeof(id) weakObjc = obj;
NSLog(@"%zd",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)));
在調(diào)用retainCount
之前調(diào)用了objc_loadWeakRetained
:
3.2.2.1 objc_loadWeakRetained
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// fixme std::atomic this load
//通過 weak 指針獲取 obj 臨時變量典徊。此時引用計數(shù)仍然不變杭煎。
obj = *location;
if (obj->isTaggedPointerOrNil()) return obj;
table = &SideTables()[obj];
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
//可以嘗試 table->unlock() 然后讀取 _objc_rootRetainCount(obj)
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
ASSERT(cls->isInitialized());
//引用計數(shù) +1恩够,此時 obj 的引用計數(shù)變了。
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls);
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
result = nil;
}
}
else {
table->unlock();
class_initialize(cls, obj);
goto retry;
}
}
//這個時候返回的`retainCount`就多了1羡铲。
table->unlock();
return result;
}
- 獲取弱引用指針對應(yīng)的
obj
蜂桶。 - 調(diào)用
rootTryRetain
對obj
引用計數(shù)+1
。
驗證:
在調(diào)用了objc_loadWeakRetained
后調(diào)用了retainCount
獲取obj
的引用計數(shù)也切。然后調(diào)用objc_release
釋放這次增加的引用計數(shù)扑媚。
weak
并不會增加引用計數(shù),CFGetRetainCount
如果獲取的是weak
指針的引用計數(shù)會先調(diào)用objc_loadWeakRetained
對對象的引用計數(shù)+1
雷恃,再調(diào)用retainCount
獲取引用計數(shù)疆股,然后調(diào)用objc_release
對對象的引用計數(shù)-1
。
那么為什么weak
的引用計數(shù)要臨時+1
呢倒槐?
為了在CFGetRetainCount
的過程中旬痹,weakObjc
不被釋放。
__weak typeof(id) weakObjc = nil;
{
NSObject *obj = [NSObject alloc];
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);
weakObjc = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);
}
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);
上面的代碼雖然會崩潰讨越,仍然可以斷點查看weakObjc
信息:
弱引用指針指向的對象已經(jīng)釋放了两残,弱引用指針還沒有釋放,出了弱引用指針作用域才釋放把跨。這么做的好處是弱引用指針的管理與對象的管理完全分開了人弓。
弱引用表調(diào)用流程:
- 弱引用指針存儲在全局
SideTables
中。 - 通過對象獲取
SideTable
取到其中的weak_table
着逐。 - 創(chuàng)建
weak_entry_t
票从,將弱引用指針包裝后加入創(chuàng)建的weak_entry_t
的referrers
(inline_referrers
)中。 - 判斷是否需要擴容
weak_table
滨嘱。 - 將創(chuàng)建的
weak_entry_t
加入weak_table
中。
四浸间、strong & unsafe_unretain
NSObject *obj = [NSObject alloc];
NSObject *obj1 = obj;
HPObject *objc = [HPObject alloc];
objc.objc = obj;
strong
修飾的屬性或者變量太雨,當(dāng)屬性變量賦值的時候會調(diào)用objc_storeStrong
(編譯時期確定):
objc_storeStrong
源碼如下:
void
objc_storeStrong(id *location, id obj)
{
//舊值
id prev = *location;
if (obj == prev) {
return;
}
//retain 新值
objc_retain(obj);
//賦值新值給指針
*location = obj;
//release 舊值
objc_release(prev);
}
在objc_storeStrong
過程中會先retain
新值然后賦值,最后release
舊值魁蒜。
在源碼中會根據(jù)變量的修飾符來確定調(diào)用的方法:
如下代碼:
@property (nonatomic, strong) NSObject *objc;
@property (nonatomic, weak) NSObject *objc1;
@property (nonatomic, unsafe_unretained) NSObject *objc3;
編譯后對應(yīng)的匯編偽代碼:
-(void)setObjc:(void *)arg2 {
objc_storeStrong(self + 0x40, arg2);
return;
}
-(void)setObjc1:(void *)arg2 {
objc_storeWeak(self + 0x48, arg2);
return;
}
-(void)setObjc3:(void *)arg2 {
self->_objc3 = arg2;
return;
}
-
strong
修飾的變量底層會調(diào)用objc_storeStrong
先進(jìn)行新值的retain
然后賦值囊扳,最后舊值進(jìn)行release
。 -
weak
底層調(diào)用objc_storeWeak
將weak
指針加入弱引用表中兜看。在dealloc
的時候會自動將weak
指針置為nil
锥咸。 -
unsafe_unretained
直接用新值賦值指針,在dealloc
的時候并不會自動置為nil
细移,可能會造成野指針訪問搏予。
總結(jié):
retain
針對相應(yīng)引用計數(shù)位+1
,開啟nonpointer
的情況下弧轧,如果引用計數(shù)出現(xiàn)上溢出雪侥,那么開始分開存儲碗殷,一半存到散列表。release
針對相應(yīng)引用計數(shù)位-1
速缨,開啟nonpointer
的情況下锌妻,如果引用計數(shù)出現(xiàn)下溢出,去散列表借來的引用計數(shù)-1
存到extra_rc
旬牲,依然下溢出則調(diào)用dealloc
仿粹。-
散列表
iPhone
真機下SideTables
中有8
張SideTable
,其它則為64
張原茅。- 每張
SideTable
包含了 引用計數(shù)表 和 弱引用表吭历。
- 引用計數(shù)表(
RefcountMap
)的Buckets
存儲了對象對應(yīng)的引用計數(shù)的包裝。 - 弱引用表(
weak_table_t
)的weak_entries
存儲了對象的弱引用指針weak_entry_t
员咽,weak_entry_t
中存儲了指向的對象和指向該對象的弱引用指針集合referrers
毒涧。referrer
中存儲了包裝的弱引用指針。 -
引用計數(shù)
TaggedPointer
直接返回對象的地址強轉(zhuǎn)為unsigned long
贝室。nonpointer
返回extra_rc + 引用計數(shù)表中引用計數(shù)
契讲。extra_rc
這里直接返回沒有+1
,之前的版本會有+1
操作滑频。alloc
之前不會對extra_rc
賦值為1
捡偏,現(xiàn)在版本會賦值為1
。- 非
nonpointer
直接返回引用計數(shù)表中引用計數(shù)峡迷。 weak
并不會增加引用計數(shù)银伟,CFGetRetainCount
會調(diào)用objc_loadWeakRetained
對weak
指向的對象引用計數(shù)+1
,調(diào)用完retainCount
后調(diào)用objc_release
對引用計數(shù)-1
绘搞。
- 弱引用表與對象是分開管理的彤避,各自在作用域處理自身邏輯。