參考文章
iOS引用計(jì)數(shù)管理之揭秘計(jì)數(shù)存儲(chǔ)
OC源碼 —— retain和release
文章目錄
參考文章
tips
一、TaggedPointer和Non-pointer
1.1.TaggedPointer
1.1.1. taggedPointer數(shù)據(jù)在內(nèi)存中的地址
1.1.2. 一些別的
1.1.3. 臨界值測(cè)試——taggedPointer的存儲(chǔ)形式
1.2. 優(yōu)化的isa——Non-poniter
1.3. 是否開(kāi)啟non-pointer時(shí)isa的賦值
二、進(jìn)入正題
2.1.Tagged Pointer對(duì)象
2.2. 開(kāi)啟了Non-pointer的isa
2.2.1. retain
源碼
不溢出
一些提到的方法
實(shí)現(xiàn)功能
溢出
用到的方法
實(shí)現(xiàn)功能
由源碼可知實(shí)現(xiàn)原理
2.2.2. release
2.3 未開(kāi)啟Non-pointer isa
總結(jié)
weak置nil
注釋部分
tips
- 如果對(duì)象不是Tagged Pointer且關(guān)閉了Non-pointer拧篮,那該對(duì)象的引用計(jì)數(shù)就使用SideTable來(lái)存荠呐。
- isa中存放retainConut楞艾,存放在uintptr_t extra_rc辉川,(“uintptr_t:一個(gè)能夠存儲(chǔ)指針的無(wú)符號(hào)int芦岂。這通常意味著它與指針的大小相同”)(最后的注釋部分是我搜到的 沒(méi)咋看明白)。最大值為2^8-1宠哄,也就是255壹将,也就是8位二進(jìn)制碼全是1時(shí)的情況,所以應(yīng)該是占一字節(jié)毛嫉。
- 當(dāng)isa中存放滿(mǎn)了時(shí)诽俯,就分一半到sideTable,isa中繼續(xù)存承粤,再次存滿(mǎn)后暴区,又分一半給sidetable。
- extra_rc存放的是引用計(jì)數(shù)的值減1
一辛臊、TaggedPointer和Non-pointer
1.1.TaggedPointer
1.Tagged Pointer專(zhuān)門(mén)用來(lái)存儲(chǔ)小的對(duì)象仙粱,例如NSNumber和NSDate 2:Tagged
2.Pointer指針的值不再是地址了,而是真正的值彻舰。所以伐割,實(shí)際上它不再是一個(gè)對(duì)象了,它只是一個(gè)披著對(duì)象皮的普通變量而已淹遵。所以口猜,它的內(nèi)存并不存儲(chǔ)在堆中,也不需要malloc和free透揣。
3.在內(nèi)存讀取上有著3倍的效率济炎,創(chuàng)建時(shí)比以前快106倍。
目前我所知的系統(tǒng)中可以啟用Tagged Pointer的類(lèi)對(duì)象有:NSDate辐真、NSNumber须尚、NSString。
1.1.1. taggedPointer數(shù)據(jù)在內(nèi)存中的地址
NSLog(@"%p",@(@(1).intValue));//0x127
NSLog(@"%p",@(@(2).intValue));//0x227
由此可知int類(lèi)型的tag為27侍咱,因?yàn)槿サ?7后0x1 = 1耐床,0x2 = 2,正好是值楔脯。所以@(1)撩轰、@(2)也不是一個(gè)對(duì)象,只是一個(gè)普通變量昧廷。
1.1.2. 一些別的
int 17:10001堪嫂,5位;
long long 37:100101木柬,6位皆串;
double 57:111001,6位眉枕。
…
1.1.3. 臨界值測(cè)試——taggedPointer的存儲(chǔ)形式
有以下測(cè)試可知恶复,double數(shù)據(jù)型的taggedPointer臨界值為@(pow(2, 55) - 3)
NSLog(@"%p",@(pow(2, 55) - 3));//0x7ffffffffffffc57
NSLog(@"%p",@(pow(2, 55) - 2));//0x6030002c50c0
*(不太理解為什么是-3怜森,我感覺(jué)應(yīng)該是-1,這樣他表示的最大值就是符合常理的數(shù)據(jù)位全是1)
2的55次方-3谤牡,結(jié)果為55位二進(jìn)制碼副硅,符號(hào)位占一位,就是56位拓哟,也就是7字節(jié)想许,而double數(shù)據(jù)存儲(chǔ)在內(nèi)存中就是8字節(jié)伶授,所以我們推測(cè)断序,tag值應(yīng)該占一字節(jié),也就是8位糜烹。
- 這個(gè)單純就是一個(gè)地址了违诗,沒(méi)有57這個(gè)tag了,里面并沒(méi)有存值的內(nèi)容疮蹦,所以Tagged Pointer失效了诸迟。
1.2. 優(yōu)化的isa——Non-poniter
舊版的isa
typedef struct objc_object *id
struct objc_object {
Class _Nonnull isa;
}
目前的isa
struct objc_object {
private:
isa_t isa;
...
}
isa_t isa = {
Class class = Person;
uintptr_t bits = 8303516107940673;
struct {
uintptr_t nonpointer = 1;
uintptr_t has_assoc = 0;
uintptr_t has_cxx_dtor = 0;
uintptr_t shiftcls = 536872040;
uintptr_t magic = 59;
uintptr_t weakly_referenced = 0;
uintptr_t deallocating = 0;
uintptr_t has_sidetable_rc = 0;
uintptr_t extra_rc = 0;
}
}
參數(shù)介紹:
1.nonpointer
0,代表普通的指針愕乎,存儲(chǔ)著Class阵苇、Meta-Class對(duì)象的內(nèi)存地址
1,代表優(yōu)化過(guò)感论,使用位域存儲(chǔ)更多的信息
2.has_assoc
是否有設(shè)置過(guò)關(guān)聯(lián)對(duì)象绅项,如果沒(méi)有,釋放時(shí)會(huì)更快
3.has_cxx_dtor
是否有C++的析構(gòu)函數(shù)(.cxx_destruct)比肄,如果沒(méi)有快耿,釋放時(shí)會(huì)更快
4.shiftcls
存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址信息
5.magic
用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化
6.weakly_referenced
是否有被弱引用指向過(guò)芳绩,如果沒(méi)有掀亥,釋放時(shí)會(huì)更快
7.deallocating
對(duì)象是否正在釋放
8.extra_rc
里面存儲(chǔ)的值是引用計(jì)數(shù)器減1
9.has_sidetable_rc
引用計(jì)數(shù)器是否過(guò)大無(wú)法存儲(chǔ)在isa中
如果為1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫SideTable的類(lèi)的屬性中
不使用non-pointer的isa可以簡(jiǎn)化為
isa_t isa = {
Class class = Person;
}
1.3. 是否開(kāi)啟non-pointer時(shí)isa的賦值
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
if (!nonpointer) {
isa.cls = cls;
} else {
isa_t newisa(0);
......
(成員賦值)
......
isa = newisa;
}
}
二妥色、進(jìn)入正題
按以下順序搪花,滿(mǎn)足1則不判斷2,依次類(lèi)推嘹害。
1:對(duì)象是否是Tagged Pointer對(duì)象撮竿;
2:對(duì)象是否啟用了Non-pointer;
3:對(duì)象未啟用Non-pointer吼拥。
2.1.Tagged Pointer對(duì)象
先說(shuō)結(jié)論:對(duì)于Tagged Pointer對(duì)象倚聚,并沒(méi)有任何的引用計(jì)數(shù)操作,引用計(jì)數(shù)數(shù)量也只是單純的返回自己地址罷了凿可。
retain時(shí)惑折。
id objc_object::rootRetain(bool tryRetain, bool handleOverflow) {
if (isTaggedPointer()) return (id)this;
...
}
release時(shí)授账。
bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) {
if (isTaggedPointer()) return false;
...
}
retainCount時(shí)。
uintptr_t objc_object::rootRetainCount() {
if (isTaggedPointer()) return (uintptr_t)this;
...
}
2.2. 開(kāi)啟了Non-pointer的isa
這里寫(xiě)的簡(jiǎn)單惨驶,上面部分寫(xiě)的詳細(xì)一點(diǎn)白热。
如果對(duì)象開(kāi)啟了Non-pointer,那么引用計(jì)數(shù)是存在isa中的粗卜,引用計(jì)數(shù)超過(guò)255將附加SideTable輔助存儲(chǔ)屋确。
2.2.1. retain
源碼
[NSObject retain];
- (id)retain {
return ((id)self)->rootRetain();
}
id objc_object::rootRetain()
{
return rootRetain(false, false);
}
id objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
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)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);
if (slowpath(carry)) {
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
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)));
if (slowpath(transcribeToSideTable)) {
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
源碼中carry的意思是溢出,溢出也就是isa中存不下了续扔,isa中的參數(shù)uintptr_t has_sidetable_rc : 1攻臀。
不溢出
首先我們看不溢出的情況
id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
isa_t oldisa;
isa_t newisa;
do {
oldisa = LoadExclusive(&isa.bits); //獲取isa的bit信息,也就是isa中的內(nèi)容
newisa = oldisa;
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
return (id)this;
}
一些提到的方法
1.LoadExclusive: 用于返回isa的內(nèi)容
LoadExclusive(&isa.bits)
static uintptr_t LoadExclusive(uintptr_t *src)
{
return *src;
}
2.addc
addc(newisa.bits, RC_ONE, 0, &carry)
# define RC_ONE (1ULL<<56)
static uintptr_t addc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout)
{
return __builtin_addcl(lhs, rhs, carryin, carryout);
}
這個(gè)方法沒(méi)搜到纱昧,官方不對(duì)外公開(kāi)刨啸,根據(jù)OC源碼 —— retain和release測(cè)試
實(shí)現(xiàn)功能
其實(shí)就是給extra_rc+1,然后更新一下isa的內(nèi)容识脆。
溢出
id objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
isa_t oldisa;
isa_t newisa;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain); //內(nèi)部是調(diào)用了rootretain设联,知識(shí)第二個(gè)參數(shù)由true變?yōu)閒alse。
}
}
通過(guò)rootRetain_overflow內(nèi)部是調(diào)用了rootretain灼捂,知識(shí)第二個(gè)參數(shù)由true變?yōu)閒alse离例。所以實(shí)際源碼是
id objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
isa_t oldisa;
isa_t newisa;
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
sidetable_addExtraRC_nolock(RC_HALF);
return (id)this;
}
# define RC_HALF (1ULL<<7)
用到的方法
rootRetain_overflow():內(nèi)部是調(diào)用了rootretain,知識(shí)第二個(gè)參數(shù)由true變?yōu)閒alse悉稠。
id objc_object::rootRetain_overflow(bool tryRetain)
{
return rootRetain(tryRetain, true);
}
sidetable_addExtraRC_nolock: 溢出后宫蛆,溢出除了存放在isa中的值變?yōu)橐话耄O轮档奶幚怼?/p>
sidetable_addExtraRC_nolock(RC_HALF);
bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
SideTable& table = SideTables()[this];
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;
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;
}
}
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1)
#define SIDE_TABLE_RC_ONE (1UL<<2)
#define SIDE_TABLE_RC_SHIFT 2
實(shí)現(xiàn)功能
更新了newisa的extra_rc和has_sidetable_rc字段偎球,將extra_rc設(shè)置為了RC_HALF也就是128洒扎,has_sidetable_rc設(shè)為true,將一半的引用計(jì)數(shù)存放到SideTable中衰絮。當(dāng)isa再次溢出時(shí)袍冷,addc之后又加了128進(jìn)去。說(shuō)白了每次extra_rc溢出了猫牡,SideTable中就增加128胡诗。
由源碼可知實(shí)現(xiàn)原理
isa的extra_rc用來(lái)存放引用計(jì)數(shù)值,has_sidetable_rc用來(lái)表示有無(wú)用sideTable存引用計(jì)數(shù)淌友。
當(dāng)isa中的extra_rc值小于等于255時(shí)煌恢,存放在extra_rc中,此時(shí)has_sidetable_rc值為0震庭,而超過(guò)這個(gè)值后瑰抵,超過(guò)的那一刻has_sidetable_rc的值變?yōu)?,把isa的extra_rc中的值分一半給sidetable器联,然后繼續(xù)往isa中存二汛,再次存滿(mǎn)時(shí)婿崭,就再往sidetable中分一半。也就是說(shuō)每次isa的extra_rc存滿(mǎn)肴颊,就變成128氓栈,剩下的加到sidetable中。
2.2.2. release
在深入理解了retain之后婿着,再看release的源碼就很簡(jiǎn)單了授瘦。代碼太長(zhǎng)我就不貼了,總結(jié)一下release的流程:
- 最普通的情況竟宋,直接將extra_rc減1
- 如果extra_rc為0提完,判斷has_sidetable_rc
- has_sidetable_rc = false,說(shuō)明對(duì)象已經(jīng)沒(méi)有引用計(jì)數(shù)了袜硫,直接dealloc釋放內(nèi)存
- has_sidetable_rc = true氯葬,說(shuō)明extra_rc有過(guò)溢出
- 從SideTable中借位成功挡篓,每次取RC_HALF婉陷,也就是128,減1之后賦給extra_rc官研,回到步驟1
- 從SideTable中借位失敗秽澳,直接dealloc
2.3 未開(kāi)啟Non-pointer isa
存到sideTable中,這部分源碼詳見(jiàn)iOS引用計(jì)數(shù)管理之揭秘計(jì)數(shù)存儲(chǔ)
總結(jié)
這片博客講的是引用計(jì)數(shù)的存放位置戏羽,做以下總結(jié):
針對(duì)數(shù)據(jù)分為三種:TaggedPointer對(duì)象担神,未開(kāi)啟non-pointer的isa對(duì)象,開(kāi)啟non-pointer的isa對(duì)象始花。
1.TaggedPointe對(duì)象妄讯,retainCount返回自己本身,也就是說(shuō)酷宵,對(duì)于Tagged Pointer對(duì)象亥贸,并沒(méi)有任何的引用計(jì)數(shù)操作,引用計(jì)數(shù)數(shù)量也只是單純的返回自己地址罷了浇垦。
2.開(kāi)啟了non-pointer的isa:先存放在isa的extra_rc中炕置,存滿(mǎn)之后,isa的 has_sidetable_rc值由0變?yōu)?男韧,分一半到sideTable中朴摊,然后繼續(xù)往isa的extra_rc中,存滿(mǎn)之后繼續(xù)分一半此虑。如果sideTable中也存滿(mǎn)了甚纲,sideTable的RefCountMap的pinned由0變?yōu)?,表示不能再增加了朦前。
3.未開(kāi)啟non-pointer的isa介杆,存放在sideTable中讹弯。
weak置nil
runtime維護(hù)著一個(gè)weak表即hash表,用于存儲(chǔ)指向?qū)ο蟮膚eak指針
Weak表是Hash表这溅,Key是所指對(duì)象的地址组民,Value是Weak指針地址的數(shù)組
以對(duì)象的地址作為key,去找weak指針
觸發(fā)調(diào)用arr_clear_deallocating 函數(shù) 悲靴,根據(jù)對(duì)象的地址將所有weak指針地址的數(shù)組臭胜,遍歷數(shù)組把其中的數(shù)據(jù)置為nil。
注釋部分
什么是uintptr_t數(shù)據(jù)類(lèi)型癞尚?
uintptr_t extra_rc = 0;(8位耸三,所以最多存到255,到256時(shí)要存到別的地方)
第一件事浇揩,在問(wèn)題提出的時(shí)候仪壮,uintptr_t不在C++中。在C99中<stdint.h>胳徽,作為可選類(lèi)型积锅。許多C++03編譯器
確實(shí)提供了該文件。它也在C++11中养盗,在缚陷,這里它還是可選的,它引用C99作為定義往核。
在C99中箫爷,它被定義為“一個(gè)無(wú)符號(hào)整數(shù)類(lèi)型,它的屬性是聂儒,任何指向void的有效指針都可以轉(zhuǎn)換為此類(lèi)型虎锚,然后再轉(zhuǎn)換回指針為void,結(jié)果將與原始指針相比較”衩婚。
把這個(gè)當(dāng)成它說(shuō)的意思窜护。它沒(méi)有提到任何關(guān)于尺寸的東西。
uintptr_t可能與void它可能更大谅猾。盡管這樣的C++實(shí)現(xiàn)方法是不正常的柄慰,但它可能會(huì)更小。例如税娜,在某個(gè)假設(shè)的平臺(tái)上void是32位坐搔,但是只使用了24位虛擬地址空間,您可以擁有24位敬矩。uintptr_t滿(mǎn)足了要求概行。我不知道為什么一個(gè)實(shí)現(xiàn)會(huì)這樣做,但標(biāo)準(zhǔn)允許這樣做弧岳。
它是一個(gè)能夠存儲(chǔ)指針的無(wú)符號(hào)int凳忙。這通常意味著它與指針的大小相同业踏。它是在C++11和更高版本的標(biāo)準(zhǔn)中定義的。
想要一個(gè)能夠保存架構(gòu)指針類(lèi)型的整數(shù)類(lèi)型的一個(gè)常見(jiàn)原因是對(duì)指針執(zhí)行特定于整數(shù)的操作涧卵,或者通過(guò)提供一個(gè)整數(shù)“句柄”來(lái)模糊指針的類(lèi)型勤家。