iOS引用計數管理之揭祕計數儲存
前言
最近偶爾出去面試瞭解一下現在iOS行情和麵試會問的問題。其中有這樣的一個問題被問到很多次:引用計數原理。回去查資料發現當時回答的很糟糕,於是就在這裡單獨寫一篇文章記錄下來。這篇文章只講一個問題:引用計數的數量存哪裡的,文末提到的其他問題後面會單獨再寫。
預備知識
要說清楚這個問題,我們需要先來了解下面的三個知識點。
除錯環境如下。
macOS:10.13.4;
XCode:9.4;
除錯裝置:My Mac。
Tagged Pointer
這個玩意的詳細解釋在這裡,簡單的說64位系統下,對於值小(多小?後面有講解)的物件指標本身已經存了值的內容了,而不用去指向一個地址再去取這個地址所存物件的值;相信你也知道了,如果是Tagged Pointer的話就少了建立物件的操作。
我們也可以在WWDC2013的《Session 404 Advanced in Objective-C》視訊中,看到蘋果對於Tagged Pointer特點的介紹:
1:Tagged Pointer專門用來儲存小的物件,例如NSNumber和NSDate
2:Tagged Pointer指標的值不再是地址了,而是真正的值。所以,實際上它不再是一個物件了,它只是一個披著物件皮的普通變數而已。所以,它的記憶體並不儲存在堆中,也不需要malloc和free。
3:在記憶體讀取上有著3倍的效率,建立時比以前快106倍。
我們來測試一下。
NSLog(@"%p",@(@(1).intValue));//0x127
NSLog(@"%p",@(@(2).intValue));//0x227
由此可知int型別的tag為27,因為去掉27後0x1 = 1,0x2 = 2,正好是值。
NSLog(@"%p",@(@(1).doubleValue));//0x157
NSLog(@"%p",@(@(2).doubleValue));//0x257
由此可知double型別的tag為57,因為去掉27後0x1 = 1,0x2 = 2,正好是值。
明顯0x127、0x257不是一個地址,所以@(1)、@(2)也不是一個物件,只是一個普通變數。
既然是Tagged Pointer那肯定得有一個tag,經過測試發現值型別不一樣所具有的tag也會相對不一樣。
為什麼說相對,因為測試發現unsigned long和long long具有相同的tag值37。
當然其他型別也有一樣的。
什麼時候NSNumber物件Tagged Pointer失效呢?那就是當值和tag加起來佔用的位元組數要超過地址長度(8位元組64位)時會失效:
為什麼說要超過,而不是超過,這個我也比較糾結,具體的看看下面的例子。
這裡針對double型別來舉個例子,其他型別的結果可能稍有不同,因為上面說到tag有不同的值,所佔用二進位制位長度會不一樣。
int 17:10001,5位;
long long 37:100101,6位;
double 57:111001,6位。
...
這樣64減去已佔用的tag位,剩下的位來表示值,所能表示的範圍也不一樣。
double pow(double, double)返回的是double型別的值。
NSLog(@"%p",@(pow(2, 55) - 3));//0x7ffffffffffffc57
57是double型別的tag,0x7ffffffffffffc57去掉tag剩下的是0x7ffffffffffffc =
pow(2, 55) - 3 = 36028797018963964;二進位制表示為0...0(9個)1...1(53個)00(2個)。
關於這裡為什麼要-3這就是我比較糾結的原因,因為二進位制表示後面還有2個0啊,還可以多表示3啊;
系統這麼做肯定有自己的考慮,也許是我理解錯了,希望你來指正。
NSLog(@"%p",@(pow(2, 55) - 2));//0x6030002c50c0
這個單純就是一個地址了,沒有57這個tag了,裡面並沒有存值的內容,所以Tagged Pointer失效了。
從這個例子可以知道tag佔用8位,64 - 55 = 9,9 - 1 = 8,因為第一位是來做符號位表示正負數的;
上面我們測試出來57佔用6個二進位制位,為什麼這裡值最長佔用56二進位制長度呢,我也不知道。
關於Tagged Pointer是否啟用,你也可以通過下面的語句來列印,這個語句是runtime原始碼中的。
NSNumber *number = @(pow(2, 55) - 3);
NSLog(@"%d",((uintptr_t)number & 1UL) == 1UL);//true
number = @(pow(2, 55) - 2);
NSLog(@"%d",((uintptr_t)number & 1UL) == 1UL);//false
目前我所知的系統中可以啟用Tagged Pointer的類物件有:NSDate、NSNumber、NSString,上面我們只舉例了NSNumber,你可以自己下來試試另外的。
不啟用後上面的例子就會得到這樣的結果,也就表示關閉成功了。
NSLog(@"%p",@(pow(2, 55) - 3));//0x6030002ccbc0
NSLog(@"%p",@(pow(2, 55) - 2));//0x6030002ccc50
NSNumber *number = @(pow(2, 55) - 3);
NSLog(@"%d",((uintptr_t)number & 1UL) == 1UL);//false
number = @(pow(2, 55) - 2);
NSLog(@"%d",((uintptr_t)number & 1UL) == 1UL);//false
Non-pointer isa
我們一直認為例項物件的isa都指向類物件,甚至還看到這樣的原始碼。
typedef struct objc_object *id
struct objc_object {
Class _Nonnull isa;
}
其實這是之前版本的程式碼了,現在版本的程式碼早就變了。
struct objc_object {
private:
isa_t isa;
...
}
所以例項物件的isa都指向類物件這樣的說法不對。
現在例項物件的isa是一個isa_t聯合體,裡面存了很多其他的東西,相信你也猜到了引用計數也在其中;如果該例項物件啟用了Non-pointer,那麼會對isa的其他成員賦值,否則只會對cls賦值。
union isa_t {
Class cls;
...
(還有很多其他的成員,包括引用計數數量)
}
物件是否不啟用Non-pointer目前有這麼幾個判斷條件,這些都可以在runtime原始碼objc-runtime-new.m中找到邏輯。
1:包含swift程式碼;
2:sdk版本低於10.11;
3:runtime讀取image時發現這個image包含__objc_rawisa段;
4:開發者自己新增了OBJC_DISABLE_NONPOINTER_ISA=YES到環境變數中;
5:某些不能使用Non-pointer的類,GCD等;
6:父類關閉。
我們自己新建一個Person類,通過OBJC_DISABLE_NONPOINTER_ISA=YES/NO來看看isa結構體的具體內容,設定方法上面有截圖。
設定了OBJC_DISABLE_NONPOINTER_ISA不一定就使用Non-pointer了,因為上面說到了還有其他條件會關閉Non-pointer。
所以我們自己建立一個Person類繼承自NSObject,這樣只通過在環境變數中設定OBJC_DISABLE_NONPOINTER_ISA
就可以控制Person物件是否啟用Non-pointer,因為我們排除了除4之外的其他條件。
不使用Non-pointer的isa
isa_t isa = {
Class class = Person;
uintptr_t bits = 4294976320;
struct {
uintptr_t nonpointer = 0;
uintptr_t has_assoc = 0;
uintptr_t has_cxx_dtor = 0;
uintptr_t shiftcls = 536872040;
uintptr_t magic = 0;
uintptr_t weakly_referenced = 0;
uintptr_t deallocating = 0;
uintptr_t has_sidetable_rc = 0;
uintptr_t extra_rc = 0;
}
}
其實可以簡化為
isa_t isa = {
Class class = Person;
}
因為原始碼中顯示不使用Non-pointer則只對isa的class賦值了,其他的都是預設值,而且除了class其他成員也不會在原始碼中被使用到。
使用Non-pointer的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;
}
}
extra_rc就是存的引用計數,nonpointer = 1表示啟用Non-pointer。
isa的賦值是在alloc方法呼叫時,內部會進入initIsa()方法,你可以進去看一看有啥不同之處。
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
if (!nonpointer) {
isa.cls = cls;
} else {
isa_t newisa(0);
......
(成員賦值)
......
isa = newisa;
}
}
SideTable
雜湊表,這是一個比較重要的資料結構,相信你也猜到了這個和物件引用計數有關;如果該物件不是Tagged Pointer且關閉了Non-pointer,那該物件的引用計數就使用SideTable來存。我們先來看一下SideTable結構體定義,至於怎麼被使用的且聽我慢慢道來。
struct SideTable {
//鎖
spinlock_t slock;
//強引用相關
RefcountMap refcnts;
//弱引用相關
weak_table_t weak_table;
...
}
啟動應用後,我們第一次看到SideTable其實是在runtime讀取image的時候。
void map_images_nolock(unsigned mhCount, const char* const mhPaths[],
const struct mach_header *const mhdrs[]) {
...
static bool firstTime = YES;
if (firstTime) {
AutoreleasePoolPage::init();
SideTableInit();
}
...
}
static void SideTableInit() {
new (SideTableBuf)StripedMap<SideTable>();
}
map_images_nolock會多次呼叫,因為ImageLoader一批載入很多個image到記憶體,然後通知runtime去讀取這一批image,沒錯這時候runtime開始從image中處理類了;SideTableInit()方法只會執行一次。SideTableInit內部用到了SideTableBuf,SideTableBuf的定義如下。
alignas(StripedMap<SideTable>) static uint8_t
SideTableBuf[sizeof(StripedMap<SideTable>)];
sizeof(StripedMap<SideTable>) = 4096;
alignas(StripedMap<SideTable>)是位元組對齊的意思,表示讓陣列中每一個元素的起始位置對齊到4096的倍數,也把陣列中每一個元素都變成了4096大小,能理解吧。
所以這句話就簡化為static uint8_t SideTableBuf[4096],也就是定義了一個4096大小型別為uint8_t的陣列,每一個元素大小為4096,名字為SideTableBuf;
現在來理解SideTableInit()中的new (SideTableBuf)StripedMap<SideTable>()。你會發現這句話沒有
任何意思,你註釋後一樣可以正常執行。因為上面那句話已經初始化SideTableBuf了,怎麼說?看下面。
在SideTableBuf定義上方有這樣的一段註釋。
We cannot use a C++ static initializer to initialize SideTables because
libc calls us before our C++ initializers run. We also don't want a global
pointer to this struct because of the extra indirection.
Do it the hard way.
我來翻譯一下:我們不能用C++靜態初始化方法去初始化SideTables,
因為C++初始化方法執行之前libc就會呼叫我們;我們同樣不想用一個全域性的指標去指向SideTables,
因為需要額外的代價。但是沒辦法我們只能這樣。
看不懂沒關係,下面就是答案。
什麼是C++ static initializer呢,我們依然可以在runtime原始碼中找到答案。在objc-os.mm中有這樣的程式碼。
size_t count;
Initializer *inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
是的,這個就是在呼叫C++的initializer了,這個操作在map_images_nolock之前執行,也就是這時候還沒有執行SideTableInit()。
我們列印出其中一個方法名。
......
libobjc.A.dylib`defineLockOrder() at objc-os.mm:674
......
然後我們去defineLockOrder()方法中打個斷點,跟蹤一波。
__attribute__((constructor)) static void defineLockOrder() {
......
SideTableLocksPrecedeLock(&crashlog_lock);
......
}
void SideTableLocksPrecedeLock(const void *newlock) {
SideTables().precedeLock(newlock);
}
然後會進入這個方法。
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
你會發現這裡已經在使用SideTableBuf了,說明SideTableBuf肯定提前被賦值了。而我們剛才說了SideTableInit()方法呼叫是C++的initializer呼叫之後,這也就是註釋說的內容。
白話文翻譯一下:通過SideTableInit()來初始化SideTable是不對的,因為在SideTableInit()之前會先執行C++的initializer,而在那個時候就已經用到SideTable了,所以我們才用靜態全域性變數來初始化SideTable,檔案被載入就會初始化。
你會在runtime原始碼中經常看到這樣的程式碼,其實剛才說到:C++的initializer呼叫階段也用到了。
SideTable &table = SideTables()[this];
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
所以我們很有必要理解這句話什麼意思。StripedMap是一個模版類,熟悉C++的應該非常熟悉這個,來看看StripedMap<SideTable>會生成怎樣的一個類。
//簡化版本,巨集啥的都替換了
class StripedMap {
//存SideTable的結構體
struct PaddedT {
SideTable value;
};
PaddedT array[64];
//取得p的雜湊值,p就是例項物件的地址
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % 64;
}
public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap< SideTable >>(this)[p];
}
...
}
這樣一來就很清晰了,StripedMap裡面有一個PaddedT陣列,StripedMap過載了[]符號,根據引數的雜湊值取PaddedT陣列的內容,陣列裡存的就是SideTable。
現在來理解reinterpret_cast什麼意思。
reinterpret_cast:轉換一個指標為其它型別的指標等,我們沒必要去深究,這樣理解就夠了。
所以
SideTable &table = SideTables()[this];
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
意思就是:將SideTableBuf轉換為StripedMap<SideTable>*型別並返回,也就
是把SideTableBuf當成StripedMap使用,這也是為什麼要寫
alignas(StripedMap<SideTable>)的原因,這樣SideTableBuf陣列每一個元素都正好
對應一個StripedMap<SideTable>物件。
這裡要特別注意了,會有雜湊衝突嗎?
我們建立兩個不同的類Person和Car,列印一下通過indexForPointer得到的雜湊值。
雜湊值計算公式:((addr >> 4) ^ (addr >> 9)) % 64;addr就是例項物件的地址。這個公式歲隨便寫的吧,看不出啥端倪。
Person *one = [[Person alloc] init];
NSLog(@"%p",one);//0x60200000bf30 105690555268912
indexForPointer(105690555268912) = 44;
Car *two = [[Car alloc] init];
NSLog(@"%p",two);//0x6030002c9710 105759277618960
indexForPointer(105759277618960) = 58;
計算出來的雜湊值確實是不一樣的,我們可以手動更改雜湊演算法把雜湊值都設定為1,看看程式是否能正常執行。
也就是更改這個方法。
static unsigned int indexForPointer(const void *p) {
return 1;
}
然後我們列印one和two的retainCount看是否正確。
[one retain];
NSLog(@"%d",[one retainCount]);//2
[two retain];
NSLog(@"%d",[two retainCount]);//2
看來都沒問題,那麼系統是怎麼解決雜湊衝突併成功的進行存取值的呢?我們下面講。
進入正題
下面我們就開始看看物件的引用計數到底存哪裡了。我先把判斷優先順序寫一下。
1:物件是否是Tagged Pointer物件;
2:物件是否啟用了Non-pointer;
3:物件未啟用Non-pointer。
滿足1則不判斷2,依次類推。
Tagged Pointer物件
retain時。
id objc_object::rootRetain(bool tryRetain, bool handleOverflow) {
if (isTaggedPointer()) return (id)this;
...
}
release時。
bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) {
if (isTaggedPointer()) return false;
...
}
retainCount時。
uintptr_t objc_object::rootRetainCount() {
if (isTaggedPointer()) return (uintptr_t)this;
...
}
由此可見對於Tagged Pointer物件,並沒有任何的引用計數操作,引用計數數量也只是單純的返回自己地址罷了。
開啟了Non-pointer
retain時。
id objc_object::rootRetain(bool tryRetain, bool handleOverflow) {
...
//其實就是對isa的extra_rc變數進行+1,前面說到isa會存很多東西
addc(newisa.bits, 1, 0, &carry);
...
}
release時。
bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) {
...
//其實就是對isa的extra_rc變數進行-1
subc(newisa.bits, 1, 0, &carry);
...
}
retainCount時。
uintptr_t objc_object::rootRetainCount() {
...
//其實就是獲取isa的extra_rc值再+1,alloc新建一個物件時bits.extra_rc為0並不是1,這個要注意。
uintptr_t rc = 1 + bits.extra_rc;
...
}
如果物件開啟了Non-pointer,那麼引用計數是存在isa中的,引用計數超過255將附加SideTable輔助儲存。
更新:看網路上該系列文章時發現自己漏了一個細節,那就是extra_rc是有儲存限制,經過測試為255,如果超過255將會附加SideTable輔助儲存。詳細解釋看這裡。
未開啟Non-pointer isa
這個是最麻煩的,因為要用到SideTable,裡面一大堆邏輯;我們拿上面的Person舉例,請記住物件的地址。
retain時。
id objc_object::rootRetain(bool tryRetain, bool handleOverflow) {
...
sidetable_retain();
...
}
id objc_object::sidetable_retain() {
SideTable& table = SideTables()[this];
}
在這裡不得不講清楚SideTable的內部實現了,如果不講清楚則沒辦法繼續看程式碼。
SideTable中有三個結構體。
spinlock_t:鎖,這個就不用說了,一個支援多執行緒環境執行的庫肯定得考慮這個;
weak_table_t:weak表就是這個,用來處理弱引用的,不過本文不講;
RefcountMap:引用表,引用計數就是這個存的,這個要好好的說明白。
RefcountMap的定義:
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
又臭又長,本來想把類展開出來的,但是發現類會非常的大,而且很難懂;所以我這裡講一下邏輯就可以了,你感興趣可以深入看看。
當我們第一次通過SideTables()[this]取得table時,這個table中refcnts內容是空的。
(我們省略spinlock_t和weak_table_t):
SideTable table = {
...
RefcountMap refcnts = {
BucketT *Buckets = NULL;
unsigned NumEntries = 0;
unsigned NumTombstones = 0;
unsigned NumBuckets = 0;
}
...
}
接下來程式會執行size_t &refcntStorage = table.refcnts[this];這句話是在幹嘛呢?
RefcountMap繼承自DenseMapBase,DenseMapBase中過載了[]操作符,所以會進入[],下面的程式碼是我展開後的部分程式碼,方便理解。
class DenseMapBase {
...
//目的很明確,就是取得一個pair<DisguisedPtr<objc_object>, size_t>
size_t &operator[](DisguisedPtr<objc_object> &&Key) {
pair<DisguisedPtr<objc_object>, size_t> &reslut = FindAndConstruct(Key);
return reslut.second;
}
...
}
這裡要注意一個細節,因為我們傳進來的是this,而這裡是用DisguisedPtr<objc_object>來接收的。
所以會觸發DisguisedPtr的初始化方法,所以this被轉成了下面的物件。
class DisguisedPtr Key = {
uintptr_t value = 18446638383154282704;
}
下面來看FindAndConstruct()做了什麼。
pair<DisguisedPtr<objc_object>, size_t>& FindAndConstruct(const DisguisedPtr<objc_object> &Key) {
//先定義一個用於接收結果的pair物件,關於pair我們肯定很熟悉了,相當於字典的一個key-value對,
//pair.first就是例項物件地址轉換成的DisguisedPtr< objc_object >類,pair.second就是這個物件的引用計數數量。
pair<DisguisedPtr<objc_object>, size_t> *TheBucket = nil;
//如果找到了直接返回,因為TheBucket會在LookupBucketFor中被賦值
if (LookupBucketFor(Key, TheBucket))
return *TheBucket;
//沒有找到,就插入一個
return *InsertIntoBucket(Key, ValueT(), TheBucket);
}
//看看能不能找到key對於的pair<DisguisedPtr<objc_object>, size_t>
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
const pair<DisguisedPtr<objc_object>, size_t> *BucketsPtr = getBuckets();
const unsigned NumBuckets = getNumBuckets();
......
(程式碼還是不貼出來了,我相信你也不想看,總結起來就是從buckets()中找到該例項物件對應的pair<DisguisedPtr<objc_object>, size_t>)
}
總結:
1:取table時我們知道了物件的雜湊值是可能一樣的,如果雜湊值一樣那麼會得到相同的table;
2:相同的table又會根據物件轉換成的DisguisedPtr<objc_object>物件在buckets中去取pair<DisguisedPtr<objc_object>, size_t>對;如果沒有就會插入一條;
插入到buckets哪個位置呢?雜湊演算法如下。
static inline uint32_t ptr_hash(uint64_t key) {
key ^= key >> 4;
key *= 0x8a970be7488fda55;//這個是隨意寫的吧,沒發現啥特別的
key ^= __builtin_bswap64(key);
return (uint32_t)key;
}
為此我依然做了一個測試,我把ptr_hash的值都返回1,模擬雜湊衝突。
static inline uint32_t ptr_hash(uint64_t key) {
return (uint32_t)1;
}
然後我們列印one和two的retainCount看是否正確。
[one retain];
NSLog(@"%d",[one retainCount]);//2
[two retain];
NSLog(@"%d",[two retainCount]);//2
經過測試依然是正確的,說明內部會解決雜湊衝突,也說明了這個雜湊演算法並不能產生唯一的值。這也就解決了
上面留下的問題,獲取table的時候沒有解決雜湊衝突,而是在查詢pair對的時候有解決雜湊衝突,方法就是
找到下一個可用的位置,這也是很常見的雜湊衝突解決方法;另外一個方法是用連結串列存所有雜湊值一樣的value,不過系統在這裡並沒有用這種方法。
3:這個buckets的大小是會動態改變的,這也是
RefcountMap refcnts = {
BucketT *Buckets = NULL;
unsigned NumEntries = 0;
unsigned NumTombstones = 0;
unsigned NumBuckets = 0;
}中後三個變數的作用;裝逼點的說法就是讓陣列具有伸縮性,提前處理一些臨界值情況。
所以我們可以把refcnts中的Buckets看成一個陣列,根據物件地址產生的雜湊值和雜湊衝突演算法肯定能在Buckets中找到其對應的pair;我們接著往下走。
id objc_object::sidetable_retain() {
...
//取得該例項物件在該table中對應pair<DisguisedPtr<objc_object>, size_t>對中size_t的引用,預設值為0。
size_t &refcntStorage = table.refcnts[this];
//SIDE_TABLE_RC_PINNED的值在64位系統為1<<63,也就是pow(2,63)
if ((refcntStorage & SIDE_TABLE_RC_PINNED) == false) {
//SIDE_TABLE_RC_ONE的值為4,為什麼要以4為單位,我不知道,估計是控制最大引用計數的值吧。
refcntStorage += SIDE_TABLE_RC_ONE;
}
return (id)this;
}
我們知道了refcntStorage值最大為pow(2,63)-4,因為再加refcntStorage & SIDE_TABLE_RC_PINNED就為false了。
而引用計數最大為(pow(2,63) - 4) >> 2 + 1 = pow(2,61) = 2305843009213693952,為什麼要+1?後面會說。
還是需要資料說話,不然有人不相信。
unsigned long number = SIDE_TABLE_RC_PINNED;//pow(2,63)
unsigned long first = (unsigned long)pow(2, 63) - 4;
unsigned long second = (unsigned long)pow(2, 63);
unsigned long max = first >> 2;
unsigned long max111 = (unsigned long)pow(2, 61);
(lldb) po second & number
9223372036854775808
(lldb) po first & number
0
(lldb) po max
2305843009213693951
(lldb) po max111
2305843009213693952
retain我們就說完了,其實release也是這樣的邏輯。
release時。
前面的邏輯一樣,拿到例項物件對應的pair。
uintptr_t objc_object::sidetable_release(bool performDealloc) {
...
//迭代器,it指向的就是pair<DisguisedPtr<objc_object>, size_t>
RefcountMap::iterator it = table.refcnts.find(this);
...
//SIDE_TABLE_RC_ONE = 4
it->second -= SIDE_TABLE_RC_ONE;
...
}
retainCount時。
uintptr_t objc_object::sidetable_retainCount() {
SideTable& table = SideTables()[this];
//這就是上面為什麼說要+1的原因
size_t refcnt_result = 1;
//迭代器,it指向的就是pair<DisguisedPtr<objc_object>, size_t>
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
//SIDE_TABLE_RC_SHIFT = 2
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
...
}
總結
這篇文章主要是講了引用計數存在哪裡,這只是龐大的引用計數管理中一個小的細節;引用計數管理還有其他的問題,比如。
1:系統怎麼存weak變數,什麼時候把weak變數置為nil的?
2:autorelease、autoreleasepool以及RunLoop怎麼合作的?
3:ARC相對於MRC多出了什麼內容?
......
後面有時間我會寫成文章的。
後記
這篇文章我用了兩天的時間完成,自己仔細讀了三次,本人是一枚菜鳥,如果你在閱讀過程中發現不嚴謹或者錯誤的地方還請指出來;感激不盡。
相關文章
- 揭祕前端儲存前端
- iOS 開發刷題系列三:NSString 引用計數iOS
- Netty原始碼分析之ByteBuf引用計數Netty原始碼
- swift自動引用計數Swift
- iOS架構設計:揭祕MVC, MVP, MVVM以及VIPERiOS架構MVCMVPMVVM
- 物件的引用計數與dealloc物件
- Java引用計數與實現Java
- 快照是什麼?揭祕儲存快照的實現
- 垃圾回收演算法:引用計數法演算法
- Objective-C自動引用計數ARCObject
- C++ :引用計數(reference count) 實現C++
- PHP的引用計數是什麼意思?PHP
- PHP的垃圾回收機制-引用計數PHP
- Spring Boot 揭祕與實戰(二) 資料儲存篇 – MongoDBSpring BootMongoDB
- Spring Boot 揭祕與實戰(二) 資料儲存篇 – MySQLSpring BootMySql
- 雲端計算管理平臺之OpenStack塊儲存服務cinder
- 蘋果iOS系統原始碼思考:物件的引用計數儲存在哪裡?--從runtime原始碼得到的啟示蘋果iOS原始碼物件
- 揭祕Java高效隨機數生成器Java隨機
- JVM 系列文章之 物件存活分析 - 引用計數 and 可達性分析JVM物件
- Spring Boot 揭祕與實戰(二) 資料儲存篇 – MyBatis整合Spring BootMyBatis
- 智慧指標引用計數變化學習指標
- 怎麼解決引用計數 GC 的迴圈引用問題?GC
- 造數儲存過程儲存過程
- 資料庫表設計之儲存引擎資料庫儲存引擎
- 天翼雲物件儲存ZOS高可用的關鍵技術揭祕物件
- iOS通過加速計計算搖一搖次數iOS
- RocketMQ高效能之底層儲存設計MQ
- Java排序之計數排序Java排序
- PHP 編譯引數儲存PHP編譯
- C++之數字計數演算法C++演算法
- Python 設定numpy不以科學計數法儲存和顯示的方法Python
- 垃圾回收的引用計數器演算法詳解演算法
- C++智慧指標學習——小談引用計數C++指標
- C#程式設計:ref【引數按引用傳遞】C#程式設計
- [數倉]資料倉儲設計方案
- 儲存管理
- iOS 資料儲存iOS
- 容器化RDS—— 計算儲存分離 or 本地儲存