ARC在編譯期和運行期做了什么?
ARC (Automatic Reference Counting)是Objective-C在iOS 5.0之后提供的一種自動內(nèi)存管理機(jī)制。它幫助開發(fā)者管理應(yīng)用程序的內(nèi)存使用,減少了因為忘記釋放內(nèi)存導(dǎo)致的內(nèi)存泄漏問題,以及過早釋放內(nèi)存引發(fā)的程序崩潰問題。ARC工作在編譯期和運行期做了以下事情:
編譯期:
- 自動插入
Retain
(引用計數(shù)+1)和Release
(引用計數(shù)-1)、Autorelease
(延遲引用計數(shù)-1)代碼: 當(dāng)對象被創(chuàng)建或引用傳遞時,引用計數(shù)+1;當(dāng)對象不再使用時,ARC會自動插入釋放內(nèi)存的代碼,從而使引用計數(shù)-1。如果發(fā)現(xiàn)在同一個對象上執(zhí)行了多次“保留”與“釋放”操作,那么ARC有時可以成對的移除這兩個操作。 - 檢查代碼,如果發(fā)現(xiàn)明顯的所有權(quán)違規(guī)問題或者循環(huán)引用,編譯器會給出警告。編譯器還會為你生成合適的dealloc方法。
- 使用ARC后,編譯器會自動管理
autoreleasepool
,進(jìn)行合理的創(chuàng)建和釋放使內(nèi)存達(dá)到穩(wěn)定的狀態(tài),無需開發(fā)者手動管理。 - ARC更新
@property
屬性的默認(rèn)語義。像強(qiáng)(strong)
和弱(weak)
引用就是這種情況。強(qiáng)引用會自動增加對象的引用計數(shù),而弱引用則不會。
運行期:
- 在運行階段,根據(jù)對象的引用情況,自動調(diào)用
release
以及autorelease
,以減少或延遲引用計數(shù)。引用計數(shù)為0的對象會被立即釋放。 - 通過對被引用對象的追蹤,ARC能夠自動破解一部分循環(huán)引用,例如:通過引入
weak
屬性,它不會增加對象的引用計數(shù),這樣一個對象即使被另一個對象通過weak
引用,也能夠被正確釋放。 - 除釋放對象之外,ARC還負(fù)責(zé)清空所有弱引用(weak reference)的值,阻止野指針的問題發(fā)生。
另外:ARC并不是內(nèi)存管理的終極解決方案,它并不能處理所有情況。比如,如果代碼中存在強(qiáng)循環(huán)引用
,即使采用了ARC也無法自動解決。在這種情況下,開發(fā)者需要找出并打破這種循環(huán)。所以,編程者仍然需要理解引用計數(shù)和內(nèi)存管理的基本原理,合理地設(shè)計代碼,避免循環(huán)引用的發(fā)生。
block 是如何在 ARC 中工作的?
在ARC下,編譯器會根據(jù)情況自動將棧上的block復(fù)制到堆上,比如block作為函數(shù)返回值時,這樣你就不必再調(diào)用
Block Copy
。
需要注意的一件事是,在ARC下,NSString * __block myString
這樣寫的話,block
會對NSString
對象強(qiáng)引用,而不是造成懸垂指針問題。如果你要和MRC保持一致,請使用__block NSString * __unsafe_unretained myString
或(更好的是)使用__block NSString * __weak myString
。
ARC的實現(xiàn)分析
__strong
自己生成并持有
id __strong obj0 = [[NSObject alloc] init];
NSLog(@"%@", obj0);
我們轉(zhuǎn)成匯編之后發(fā)現(xiàn)整個的執(zhí)行過程如下:
主要經(jīng)歷的方法如下:
//初始化的兩個方法如下:
objc_alloc_init
objc_storeStrong
//所有程序執(zhí)行完之后:
objc_autoreleasePoolPop
所以我們直接來看storeStrong方法。
storeStrong
在runtime文件中找到這個函數(shù)
如下
objc_storeStrong(id *location, id obj)
{
//用prev保留被賦值對象原來所指向的對象
id prev = *location;
//如果所賦的值和被賦值對象所指的對象是同一個,就直接return不進(jìn)行任何操作
if (obj == prev) {
return;
}
//如果所賦的值和被賦值對象所指的對象不是同一個
//就先objc_retain使所賦的值對象的引用計數(shù)+1(因為賦值成功之后要持有)
objc_retain(obj);
//改變被賦值對象所指向的對象為新的對象
*location = obj;
//因為prev保留了被賦值對象原來所指向的對象,所以對prev進(jìn)行objc_release使原來的舊對象引用計數(shù)-1,因為現(xiàn)在我們的被賦值對象已經(jīng)不指向它了
objc_release(prev);
}
例子:(賦值操作時)
obj = otherObj;
//會變成如下函數(shù)調(diào)用
objc_storeStrong(&obj, otherObj);
其中做了四件事:
- 檢查輸入的 obj 地址 和指針指向的地址是否相同。
- 持有對象,引用計數(shù) + 1 。
- 指針指向 obj。
- 原來指向的對象引用計數(shù) - 1(釋放對象)。
對于這里傳入的NULL來說這就等同于向?qū)ο蟀l(fā)送release消息
詳細(xì)邏輯思路見上方代碼中注釋講解。
SideTable散列表
內(nèi)存管理主要結(jié)構(gòu)代碼
struct SideTable {
spinlock_t slock; // 保證原子操作的自旋鎖
RefcountMap refcnts; // 引用計數(shù)的 hash 表
weak_table_t weak_table; // weak 引用全局 hash 表
};
objc_retain
來學(xué)習(xí)一下objc_object的具體實現(xiàn)
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
再接著看最后所調(diào)用的retain()
方法:
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
系統(tǒng)會對是否支持NONPOINTER_ISA進(jìn)行不同的處理
每個OC對象都含有一個isa指針,__arm64__之前,isa僅僅是一個指針,保存著對象或類對象內(nèi)存地址,在__arm64__架構(gòu)之后,apple對isa進(jìn)行了優(yōu)化,變成了一個共用體(union)結(jié)構(gòu),同時使用位域來存儲更多的信息。
union isa_t
{
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;//->表示使用優(yōu)化的isa指針
uintptr_t has_assoc : 1;//->是否包含關(guān)聯(lián)對象
uintptr_t has_cxx_dtor : 1;//->是否設(shè)置了析構(gòu)函數(shù),如果沒有,釋放對象更快
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->類的指針
uintptr_t magic : 6;//->固定值,用于判斷是否完成初始化
uintptr_t weakly_referenced : 1;//->對象是否被弱引用
uintptr_t deallocating : 1;//->對象是否正在銷毀
uintptr_t has_sidetable_rc : 1;//1->在extra_rc存儲引用計數(shù)將要溢出的時候,借助Sidetable(散列表)存儲引用計數(shù),has_sidetable_rc設(shè)置成1,未溢出的時候為0
uintptr_t extra_rc : 19; //->存儲引用計數(shù)
};
};
支持Nonpointer isa的處理
objc_object::rootRetain()
{
return rootRetain(false, false);
}
ALWAYS_INLINE 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)) {
// 2、SideTable散列表方法
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); // 應(yīng)用計數(shù)extra_rc++
// 如果newisa.extra_rc++ 溢出, carry==1
if (slowpath(carry)) {
// 溢出
if (!handleOverflow) {
ClearExclusive(&isa.bits); // 空操作(系統(tǒng)預(yù)留)
return rootRetain_overflow(tryRetain);// 再次調(diào)用rootRetain(tryRetain,YES)
}
// 執(zhí)行rootRetain_overflow會來到這里,就把extra_rc對應(yīng)的數(shù)值(一半)存到SideTable
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF; // 溢出了,設(shè)置為一半,保存一半到SideTable
newisa.has_sidetable_rc = true; // 標(biāo)記借用SideTable存儲
}
// StoreExclusive保存newisa.bits到isa.bits,保存成功返回YES
// 這里while判斷一次就結(jié)束了
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// 借位保存:把RC_HALF(一半)存入SideTable
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
不支持的處理
objc_object::rootRetain()
{
if (isTaggedPointer()) return (id)this;
return sidetable_retain();
}
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];
// 判斷是否溢出,溢出了就是引用計數(shù)太大了,不管理了
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
// 引用計數(shù)加1,(上圖可知,偏移2位,SIDE_TABLE_RC_ONE = 1<<2)
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
可以看到不支持Nonpointer isa
的處理就是直接sidetable_retain
,這是由于計數(shù)都存儲在sidetable中了,處理邏輯較支持Nonpointer isa的情況要簡單一些。
不支持Nonpointer isa 的處理:
去sidetable取出計數(shù)信息 執(zhí)行加一操作
支持Nonpointer isa的處理:
- 首先判斷是否為標(biāo)簽指針類型 如果是 直接返回
- 進(jìn)入do-while處理邏輯
- 先判斷是否為 其一定支持
Nonpointer isa
的架構(gòu),但是isa
沒有額外信息
如果沒有額外信息 那就和不支持意義一樣(判斷是否有優(yōu)化) 引用計數(shù)存儲在sidetable
中,走sidetable
的引用計數(shù)+1的流程 - 判斷對象是否正在釋放,如果正在釋放則執(zhí)行
dealloc
流程。 - 有存儲額外信息,包含引用計數(shù)。我們嘗試對
isa
中的extra_rc++
加一進(jìn)行測試
3.1 如果沒有溢出越界的情況,我們將isa的值修改為extra_rc++
之后的值
3.2 如果有溢出 將一半的計數(shù)存儲到extra_rc
,另一半存儲到sidetable
中去 設(shè)置設(shè)置標(biāo)志位位true
retain的總結(jié):
如果isa
可以存儲額外信息,那么有extra_rc
位用來存儲引用計數(shù),當(dāng)引用計數(shù)滿了之后 就會存儲到sidetable
中 。
retain
的流程也是針對isa
是否支持存儲信息分別進(jìn)行處理
當(dāng)extra_rc
存儲溢出了,這個時候是一半(extra_rc
能表示的最大值+1的一半)在extra_rc
一半存儲在sidetable
中,這里跟release
的操作是對應(yīng)的(extra_rc
不夠減了也是去sidetable
借extra_rc
最大值的一半的計數(shù)),這樣設(shè)計的好處避免了頻繁的去sidetable
中讀取計數(shù)信息—假如我們溢出了把計數(shù)全部存到sidetable
中去,那么有release
的時候,extra_rc
也不夠減了,又去借,這就大大降低了效率,比起直接操作isa
。
這個優(yōu)化的好處就是我們省去了頻繁去sidetable
中讀取計數(shù)信息,從而大大提高了效率,這樣的話因為平時絕大多處操作都是普通的retain
和release
,所以都可以得到優(yōu)化,而我們?nèi)绻x取引用計數(shù)的值的話就相對麻煩一點,需要sidetable
和extra_rc
兩者相加,但是讀取引用計數(shù)值的方法使用率幾乎為零,也就是調(diào)試的時候會用到而已。
objc_release
來學(xué)習(xí)一下objc_release
的具體實現(xiàn)
objc_release(id obj)
{
if (obj->isTaggedPointerOrNil()) return;
return obj->release();
}
下面我們看一下真正的release
方法:
// handleUnderflow 參數(shù)看似是一個 bool 類型的表示是否處理下溢出,
// 當(dāng)溢出發(fā)生了的話是必須要處理的,如果 handleUnderflow 為 false,
// 那么它會借一個 rootRelease_underflow 函數(shù),并再次調(diào)用 rootRelease 函數(shù),
// 并把 handleUnderflow 參數(shù)傳遞 true。
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
// 如果是 Tagged Pointer 直接返回 false,Tagged Pointer 不參與引用計數(shù)處理,它內(nèi)存位于棧區(qū),由系統(tǒng)處理
if (isTaggedPointer()) return false;
// 標(biāo)記 SideTable 是否加鎖了
bool sideTableLocked = false;
// 臨時變量存放舊的 isa
isa_t oldisa;
// 臨時變量存放字段修改后的 isa
isa_t newisa;
retry:
do {
// 以原子方式讀到 &isa.bits 的數(shù)據(jù)
oldisa = LoadExclusive(&isa.bits);
// 把 oldisa 賦值給 newisa,此時 isa.bits/oldisa/newisa 三者是相同的
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
// 如果對象的 isa 只是原始指針 (Class isa/Class cls)
// __arm64__ && !__arm642__ 平臺下,取消 &isa.bits 的獨占訪問標(biāo)記
// x86_64 下什么都不需要做,對它而言上面的 LoadExclusive 也只是一個原子讀取 (atomic_load)
ClearExclusive(&isa.bits);
// 如果當(dāng)前對象是元類對象,則直接返回 false
if (rawISA()->isMetaClass()) return false;
// 如果當(dāng)前 SideTable 加鎖了則進(jìn)行解鎖
if (sideTableLocked) sidetable_unlock();
// 只針對 isa 是原始 Class cls 的對象調(diào)用的 sidetable_release 函數(shù)
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides
// 不要檢查 newisa.fast_rr; 我們之前已經(jīng)調(diào)用過所有 RR 的重載
// extra_rc--
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
// 如果發(fā)生了下溢出的話,要進(jìn)行處理,如果沒有發(fā)生的話就是結(jié)束循環(huán),解鎖并執(zhí)行 return false;
if (slowpath(carry)) {
// don't ClearExclusive()
// 不執(zhí)行 ClearExclusive()
// 這里直接 goto 到 underflow 中去處理溢出
goto underflow;
}
// 這里結(jié)束循環(huán)的方式同 rootRetain 函數(shù),都是為了保證 isa.bits 能正確修改
// StoreExclusive 和 StoreReleaseExclusive 的區(qū)別在于 memory_order_relaxed 和 memory_order_release
// 可參考 https://en.cppreference.com/w/cpp/atomic/memory_order
// 當(dāng) &isa.bits 與 oldisa.bits 相同時,把 newisa.bits 復(fù)制給 &isa.bits,并返回 true
// 當(dāng) &isa.bits 與 oldisa.bits 不同時,
// 把 oldisa.bits 復(fù)制給 &isa.bits, 并返回 false (此時會繼續(xù)進(jìn)行 do wehile 循環(huán))
} while (slowpath(!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits)));
// 如果未下溢出的話,不需要 goto underflow,如果 Sidetable 加鎖了,
// 則進(jìn)行解鎖,然后返回 false,函數(shù)執(zhí)行結(jié)束
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// newisa.extra_rc-- 發(fā)生溢出時,有兩種方式進(jìn)行處理:
// 1. 如果 SideTable 中有保存對象的引用計數(shù)的話可以從 SideTable 中借用
// 2. 如果 SideTable 中沒有保存對象的引用計數(shù)的話,表示對象需要執(zhí)行銷毀了
// abandon newisa to undo the decrement
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) {
// 如果 newisa.has_sidetable_rc 為 true,表示在 SideTable 中有保存對象的引用計數(shù)
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
// 如果 handleUnderflow 為 false,則調(diào)用 rootRelease_underflow,“遞歸” 調(diào)用 rootRelease 處理溢出
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
// 將 retain count 從 SideTable 中轉(zhuǎn)移到 isa.extra_rc 中保存。
if (!sideTableLocked) {
// 如果 SideTable 未加鎖
// 同上,清除獨占標(biāo)記
ClearExclusive(&isa.bits);
// 給 SideTable 加鎖
sidetable_lock();
// 并把加鎖標(biāo)記置為 true
sideTableLocked = true;
// Need to start over to avoid a race against the nonpointer -> raw pointer transition.
// 回到 retry
goto retry;
}
// Try to remove some retain counts from the side table.
// 嘗試從 SideTable 中移除一些引用計數(shù)。
// 是從 SideTable 借一些引用計數(shù)出來,borrowed 是借到的值,可能是 0,也可能是 RC_HALF
// refcnts 中保存的引用計數(shù)是 RC_HALF 的整數(shù)倍,
// 每次 retain 溢出時都是往 refcnts 中轉(zhuǎn)移 RC_HALF,
// 剩下的 RC_HALF 放在 extra_rc 字段中
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.
// 為了避免競態(tài),即使 SideTable 計數(shù)現(xiàn)在為零,也必須保持 has_sidetable_rc 之前的設(shè)置。
if (borrowed > 0) {
// borrowed 表示從 SideTable 借到引用計數(shù)了
// Side table retain count decreased.
// SideTable 引用計數(shù) 減少。
// Try to add them to the inline count.
// 嘗試將借來的引用計數(shù)增加到 extra_rc 中。
// 賦值。(包含減 1 的操作)
newisa.extra_rc = borrowed - 1; // redo the original decrement too
// 原子保存修改后的 isa.bits
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
// 如果失敗的話
// Inline update failed.
// extra_rc 更新失敗。
// Try it again right now.
// This prevents livelock on LL/SC architectures where the side
// table access itself may have dropped the reservation.
// 立即進(jìn)行重試。
// 這樣可以防止在 LL/SC體系結(jié)構(gòu)上發(fā)生 livelock,在這種情況下 SideTable 訪問本身可能已取消預(yù)留。
// 活鎖可參考: https://www.zhihu.com/question/20566246
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
// 把借來的引用計數(shù)增加到 extra_rc 中
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
// 如果還是失敗的話,下面 goto retry 再重來
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) {
// 如果還是失敗了。
// Inline update failed.
// Put the retains back in the side table.
// 把從 SideTable 借來的引用計數(shù)還放回到 SideTable 中去。
sidetable_addExtraRC_nolock(borrowed);
// 然后直接 goto retry; 進(jìn)行全盤重試
goto retry;
}
// Decrement successful after borrowing from side table.
// 減去從 SideTable 借來的引用計數(shù)成功。
// 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();
// 返回 false
return false;
}
else {
// SideTable 是空的,執(zhí)行 dealloc 分支
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// Really deallocate.
// 執(zhí)行銷毀。
if (slowpath(newisa.deallocating)) {
// 如果對象已經(jīng)被標(biāo)記了正在執(zhí)行釋放...
// 這里又進(jìn)行釋放,明顯是發(fā)生了過度釋放...
// 清除獨占標(biāo)記
ClearExclusive(&isa.bits);
// 如果 SideTable 加鎖了則進(jìn)行解鎖
if (sideTableLocked) sidetable_unlock();
// 調(diào)用 overrelease_error,crash 報錯...
// 對象在銷毀的過程中過度釋放;中斷 objc_overrelease_during_dealloc_error 進(jìn)行調(diào)試
return overrelease_error();
// does not actually return
}
// 把對象的 isa 的 deallocating 置為 true。isa 的又一個字段被設(shè)置了,越來的越多的字段被發(fā)現(xiàn)設(shè)置位置了。
newisa.deallocating = true;
// 設(shè)置 &isa.bits,如果失敗,則 goto retry;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
// 如果加鎖了,則進(jìn)行解鎖。
if (slowpath(sideTableLocked)) sidetable_unlock();
// 這個函數(shù)以當(dāng)前的水平實在是看不懂呀...
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
// 如果 performDealloc 為 true,則以消息發(fā)送的方式調(diào)用 dealloc
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
}
sidetable_release
// return uintptr_t instead of bool so that the various raw-isa -release paths all return zero in eax
// 返回 uintptr_t 而不是 bool,以便各種 raw-isa -release路徑在 eax 中都返回零
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
// 如果當(dāng)前平臺支持 isa 優(yōu)化
#if SUPPORT_NONPOINTER_ISA
// 如果 isa 是優(yōu)化的 isa 則直接執(zhí)行斷言,
// sidetable_release 函數(shù)只能在對象的 isa 是原始 isa 時調(diào)用(Class cls)
ASSERT(!isa.nonpointer);
#endif
// 從全局的 SideTalbes 中找到 this 所處的 SideTable
SideTable& table = SideTables()[this];
// 臨時變量,標(biāo)記是否需要執(zhí)行 dealloc
bool do_dealloc = false;
// 加鎖
table.lock();
// it 的類型是: std::pair<DenseMapIterator<std::pair<Disguised<objc_object>, size_t>>, bool>
// try_emplace 處理兩種情況:
// 1. 如果 this 在 refcnts 中還不存在,則給 this 在 buckets 中找一個 BucketT,
// KeyT 放 this, ValueT 放 SIDE_TABLE_DEALLOCATING,然后使用這個 BucketT 構(gòu)建一個 iterator,
// 然后用這個 iterator 和 true 構(gòu)造一個 std::pair<iterator, true> 返回。
// 2. 如果 this 在 refcnts 中已經(jīng)存在了,則用 this 對應(yīng)的 BucketT 構(gòu)建一個 iterator,
// 然后用這個 iterator 和 false 構(gòu)造一個 std::pair<iterator, false> 返回。
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
// refcnt 是引用計數(shù)值的引用。
// it.first 是 DenseMapIterator,它的操作符 -> 被重寫了返回的是 DenseMpaIterator 的 Ptr 成員變量,
// 然后 Ptr 的類型是 BucketT 指針,
// 然后這里的 ->second 其實就是 BucketT->second,其實就是 size_t,正是保存的對象的引用計數(shù)數(shù)據(jù)。
auto &refcnt = it.first->second;
if (it.second) {
// 如果 it.second 為 true,表示 this 第一次放進(jìn) refcnts 中,且 BucketT.second 已經(jīng)被置為 SIDE_TABLE_DEALLOCATING,
// 標(biāo)記為需要執(zhí)行 dealloc
do_dealloc = true;
} else if (refcnt < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
// 如果 refcnt < SIDE_TABLE_DEALLOCATING,那可能的情況就是 SIDE_TABLE_WEAKLY_REFERENCED 或者為 0
// 標(biāo)記為需要執(zhí)行 dealloc
do_dealloc = true;
// 與 SIDE_TABLE_DEALLOCATING 執(zhí)行或操作,表示把 refcnt 標(biāo)記為 DEALLOCATING
refcnt |= SIDE_TABLE_DEALLOCATING;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
// refcnt & SIDE_TABLE_RC_PINNED 值為 false 的話表示,
// rcfcnts 中保存的 this 對應(yīng)的 BucketT 的 size_t 還沒有溢出,還可正常進(jìn)行操作存儲 this 的引用計數(shù)
// refcnt 減去 SIDE_TABLE_RC_ONE
refcnt -= SIDE_TABLE_RC_ONE;
}
// 解鎖
table.unlock();
if (do_dealloc && performDealloc) {
// 如果 do_dealloc 被標(biāo)記為需要 dealloc 并且入?yún)?performDealloc 為 true,
// 則以 objc_msgSend 消息發(fā)送的方式調(diào)用對象的 dealloc 方法
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return do_dealloc;
}
在上面的代碼當(dāng)中有兩個宏定義:
現(xiàn)在release
也很好理解了:
- 依舊是判斷是否為
taggedPointer
,如果是,直接返回false
,不需要dealloc
- 判斷是否有優(yōu)化 如果沒有 就直接操作散列表,使引用計數(shù)-1
- 判斷是引用計數(shù)為否為0 如果是0則執(zhí)行
dealloc
流程 - 若
isa
有優(yōu)化,則對象的isa
位存儲的引用計數(shù)減一,且通過carry判斷是否向下溢出了 結(jié)果為負(fù)數(shù)(下圖有點問題 應(yīng)該是判斷是有向下溢出),如果是,如果到-1 就放棄newisa
改為old
,并將散列表中一半引用計數(shù)取出來,然后將這一半引用計數(shù)減一在存到isa
的extra_rc
- 如果
sidetable
的引用計數(shù)為0,對象進(jìn)行dealloc
流程
其實和retain
一樣 不過release
操作變成-1 并且需要注意從sidetable
中的一半減一放入
retainCount
如果對象的isa
是非指針的話,引用計數(shù)同時在 extra_rc
字段和 SideTable
中保存,要求它們的和。如果對象的isa
是原始isa
的話,對象的引用計數(shù)數(shù)據(jù)只保存在 SideTable
中。
- 當(dāng)對象的
isa
經(jīng)過優(yōu)化,首先獲取isa
位域extra_rc
中的引用計數(shù),默認(rèn)會+1(防止你沒持有就要打?。?,uintptr_t rc = 1 + bits.extra_rc;然后獲取散列表的引用計數(shù)表中的引用計數(shù),兩者相加得到對象的最終的引用計數(shù) - 當(dāng)對象的
isa
沒有經(jīng)過優(yōu)化,則直接獲取散列表的引用計數(shù)表中的引用計數(shù),返回。 - 當(dāng)我們
alloc
一個對象時,然后調(diào)用retainCount
函數(shù),得到對象的引用計數(shù)為1。這是因為在底層rootRetainCount
方法中,引用計數(shù)默認(rèn)+1了,這里只有對引用計數(shù)的讀取操作,是沒有寫入操作的,簡單來說就是:為了防止alloc
創(chuàng)建的對象被釋放(引用計數(shù)為0會被釋放),所以在編譯階段,程序底層默認(rèn)進(jìn)行了+1操作。實際上在extra_rc
中的引用計數(shù)仍然為0(因為extra_rc
中存放的引用計數(shù)值是除該對象本身之外的引用計數(shù)數(shù)量)
所以 通過alloc
或者new
這樣賦值來新建一個對象 ARC MRC環(huán)境下都是1 這個1是底層默認(rèn)的返回值加一 沒有調(diào)用retain
其他強(qiáng)引用 才會調(diào)用objc_retain
來持有
從runtime
源碼中找到retainCount
供大家參考:
inline uintptr_t
objc_object::rootRetainCount()
{
// 如果是 Tagged Pointer 的話,獲取它的引用計數(shù)則直接返回 (uintptr_t)this
if (isTaggedPointer()) return (uintptr_t)this;
// 加鎖
sidetable_lock();
// 以原子方式加載 &isa.bits 數(shù)據(jù)
isa_t bits = LoadExclusive(&isa.bits);
// 如果是 __arm64__ && !__arm64e__ 平臺下,要清除獨占標(biāo)記
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
// 如果對象的 isa 是非指針的話,引用計數(shù)同時在 extra_rc 字段和 SideTable 中保存,要求它們的和
// 這里加 1, 是因為 extra_rc 存儲的是對象本身之外的引用計數(shù)的數(shù)量(這個加1操作也就是為什么我們新alloc等初始化一個對象之后,打印它的引用計數(shù)值為1)
uintptr_t rc = 1 + bits.extra_rc;
// 如果 has_sidetable_rc 位為 1,則表示在 SideTable 中也保存有對象的引用計數(shù)數(shù)據(jù)
if (bits.has_sidetable_rc) {
// 找到對象的在 SideTable 中的引用計數(shù)并增加到 rc 中
rc += sidetable_getExtraRC_nolock();
}
// 解鎖
sidetable_unlock();
// 返回 rc
return rc;
}
sidetable_unlock();
// 如果對象的 isa 是原始 isa 的話,對象的引用計數(shù)數(shù)據(jù)只保存在 SideTable 中
return sidetable_retainCount();
}
isa如果優(yōu)化過,即支持Nonpointer isa,則在sidetable
中查找引用計數(shù)的函數(shù)如下:
size_t
objc_object::sidetable_getExtraRC_nolock()
{
// 此函數(shù)只限定 isa 是非指針的對象調(diào)用
ASSERT(isa.nonpointer);
// 從全局的 SideTables 中找到 this 所處的 SideTable
SideTable& table = SideTables()[this];
// 查找對象的引用計數(shù)
RefcountMap::iterator it = table.refcnts.find(this);
// 如果未找到,返回 0
if (it == table.refcnts.end()) return 0;
// 如果找到了做一次右移操作,后兩位是預(yù)留的標(biāo)記位
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
不支持Nonpointer isa的話,在sidetable
中查找引用計數(shù)的函數(shù)如下:
uintptr_t
objc_object::sidetable_retainCount()
{
// 找到 this 所在的 SideTable
SideTable& table = SideTables()[this];
// refcnt_result 初始為 1,因為 SideTable 中存儲的是對象本身之外的引用計數(shù)的數(shù)量
size_t refcnt_result = 1;
// 加鎖
table.lock();
// 在 refcnts 中查找對象的引用計數(shù)
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
// this is valid for SIDE_TABLE_RC_PINNED too
// 這也對 SIDE_TABLE_RC_PINNED 有效
// 移位并增加到 refcnt_result
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
// 解鎖
table.unlock();
return refcnt_result;
}
retainCount
相關(guān)流程圖如下:
在學(xué)完了release
和retain
之后,我們淺淺地總結(jié)一下: 在我們alloc
初始化完一個對象的過程中,系統(tǒng)在編譯階段,程序底層默認(rèn)對對象進(jìn)行了引用計數(shù)+1操作,但是這個1不會出現(xiàn)在sidetable
中,也不會出現(xiàn)在extra_rc
中,因為sidetable
和extra_rc
當(dāng)中存放的都是該對象本身之外的引用計數(shù)的數(shù)量,所以初始狀態(tài)sidetable
和extra_rc
中的值都是0,然后我們后續(xù)進(jìn)行的retain
和release
操作都是針對sidetable
和extra_rc
中的引用計數(shù)進(jìn)行+1或-1。
非自己生成并持有
id __strong obj = [NSMutableArray array];
NSLog(@"%@", obj);
我們發(fā)現(xiàn)出現(xiàn)了objc_retainAutoreleasedReturnValue
這個方法
接著我們就探究其原理:
先看一個例子:
@autoreleasepool {
__autoreleasing NSObject *obj = [NSObject new];
}
該代碼對應(yīng)的偽代碼是:
// 獲取哨兵POOL_SENTINEL
void * atautoreleasepoolobj = objc_autoreleasePoolPush();
{
__autoreleasing NSObject *obj = [NSObject new];
}
// 就是release哨兵之后的autorelease對象。
objc_autoreleasePoolPop(atautoreleasepoolobj);
autorelease調(diào)用棧如下:
- [NSObject autorelease]
└── id objc_object::rootAutorelease()
└─ id objc_object::rootAutorelease2()
└─ static id AutoreleasePoolPage::autorelease(id obj)
└─ static id AutoreleasePoolPage::autoreleaseFast(id obj)
├─ id *add(id obj)
├─ static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
│ ├─ AutoreleasePoolPage(AutoreleasePoolPage *newParent)
│ └─ id *add(id obj)
└─ static id *autoreleaseNoPage(id obj)
├─ AutoreleasePoolPage(AutoreleasePoolPage *newParent)
└─ id *add(id obj)
一個autorelease對象在什么時刻釋放?
答案是:
- 手動指定
Autoreleasepool
:當(dāng)前Autoreleasepool
作用域大括號結(jié)束時釋放; - 不手動指定:
autorelease
對象會被添加到最近一次創(chuàng)建的autoreleasepool
中,并在當(dāng)前的runloop
迭代結(jié)束時候釋放。
例如: 主Runloop
對Autoreleasepool
管理的流程:Runloop
中,檢測到觸摸事件,創(chuàng)建事件,創(chuàng)建Autoreleasepool
,autorelease
對象加入pool
中,事件完成,Runloop
運行循環(huán)將要結(jié)束,釋放Autoreleasepool
,向pool
中對象發(fā)送release
消息,Runloop
休眠。
autorelease 進(jìn)行的非持有方法的優(yōu)化(自動添加到自動釋放池):
-
alloc/new/copy/mutableCopy
—持有對象方法會自動添加到自動釋放池 - 其他類方法返回的對象,如下面的
createObject
就會自動添加到自動釋放池
@implementation ObjectTest
+ (instancetype)createObject {
return [self new];
}
接著我們來看這兩個方法本尊:
id objc_autoreleaseReturnValue(id obj)
{
// prepareOptimizedReturn判斷是否可以TSL優(yōu)化,可以則標(biāo)記,YES--就不需要調(diào)用 objc_autorelease(),優(yōu)化性能
if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;
return objc_autorelease(obj);
}
id objc_retainAutoreleasedReturnValue(id obj)
{
// 如果之前 objc_autoreleaseReturnValue() 存入的標(biāo)志位為 ReturnAtPlus1,則直接返回對象,無需調(diào)用 objc_retain(),優(yōu)化性能
if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
return objc_retain(obj);
}
然后是這兩個方法中if判斷的條件調(diào)用的函數(shù):
static ALWAYS_INLINE bool
prepareOptimizedReturn(ReturnDisposition disposition)
{
//獲取返回標(biāo)記
assert(getReturnDisposition() == ReturnAtPlus0);
if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
if (disposition) setReturnDisposition(disposition);
return true;
}
return false;
}
static ALWAYS_INLINE ReturnDisposition
acceptOptimizedReturn()
{
ReturnDisposition disposition = getReturnDisposition();
setReturnDisposition(ReturnAtPlus0); // reset to the unoptimized state
return disposition;
}
TLS 全稱為 Thread Local Storage,是每個線程專有的鍵值存儲:
/*在某個線程上的函數(shù)調(diào)用棧上相鄰兩個函數(shù)對 TLS 進(jìn)行了存取,這中間肯定不會有別的程序『插手』。
所以 getReturnDisposition() 和 setReturnDisposition() 的實現(xiàn)比較簡單,不需要判斷考慮是針對哪個對象的 Disposition 進(jìn)行存取,因為當(dāng)前線程上下文中只處理唯一的對象,保證不會亂掉。 */
static ALWAYS_INLINE void
setReturnDisposition(ReturnDisposition disposition)
{
tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}
callerAcceptsOptimizedReturn(__builtin_return_address(0))
函數(shù)在不同架構(gòu)的 CPU 上實現(xiàn)也是不一樣的。 主要作用:
__builtin_return_address(0)獲取當(dāng)前函數(shù)返回地址,傳入 callerAcceptsOptimizedReturn 判斷調(diào)用方是否緊接著調(diào)用了 objc_retainAutoreleasedReturnValue
當(dāng)判斷調(diào)用方緊接著調(diào)用了 objc_retainAutoreleasedReturnValue 或者 objc_unsafeClaimAutoreleasedReturnValue
直接返回當(dāng)前對象地址,而不執(zhí)行retain與autorelease操作.
其作用就是得到函數(shù)的返回地址,0–表示返回當(dāng)前函數(shù)的返回地址,1–表示返回當(dāng)前函數(shù)的調(diào)用方的返回地址;
ARC 會視情況在調(diào)用方法時可能會添加 retain ,在方法內(nèi)部返回時可能會添加 autorelease ,經(jīng)過優(yōu)化后很可能會抵消。
1、持有、無引用:
- (void)test {
[BBObject new];
}
編譯器編譯后的偽代碼:
- (void)test {
objc_release([BBObject new]) ;
}
2、持有、局部變量引用 __strong:
- (void)test {
__strong BBObject * obj = [BBObject new];
}
編譯器編譯后的偽代碼:
- (void)test {
id temp = [BBObject new];
objc_storeStrong(&temp,nil);//相當(dāng)于tmp指向?qū)ο髨?zhí)行release
}
3、持有、外部變量引用:
- (void)test {
self.obj = [BBObject new];
}
編譯器編譯后的偽代碼:
- (void)test{
id temp = [BBObject new];
[self setObj:temp];//setter方法執(zhí)行objc_storeStrong
objc_release(temp);
}
- (void)setObj:(id aObj) {
objc_storeStrong(&_obj, aObj);
}
4、不持有、無引用:
- (void)test {
[BBObject createObj];
}
編譯器編譯后的偽代碼:
+ (instancetype) createObj {
id tmp = [self new];
return objc_autoreleaseReturnValue(tmp); // 系統(tǒng)可能會調(diào)用[tmp autorelease]
}
- (void)test {
objc_unsafeClaimAutoreleasedReturnValue([BBObject createObj]);
}
5、不持有、局部變量引用:
- (void)test {
BBObject * obj1 = [BBObject createObj];
}
編譯器編譯后的偽代碼:
+ (instancetype) createObj {
id tmp = [self new];
return objc_autoreleaseReturnValue(tmp); // 系統(tǒng)可能會調(diào)用[tmp autorelease]
}
- (void)test {
id obj1 = objc_retainAutoreleasedReturnValue([BBObject createObj]);
objc_storeStrong(& obj1,nil);
}
發(fā)現(xiàn)obj1指向的對象不會加入autoreleasepool
6、不持有、外部變量引用:
+ (instancetype) createObj {
id tmp = [self new];
return objc_autoreleaseReturnValue(tmp); // 系統(tǒng)可能會調(diào)用[tmp autorelease]
}
- (void)test {
self.obj = [BBObject createObj];
}
編譯后的偽代碼:
- (void)test {
id temp = _objc_retainAutoreleasedReturnValue([Foo createFoo]);
[self setObj:temp]; // setter方法執(zhí)行objc_storeStrong
objc_release(temp);
}
總結(jié)非自己生成并持有
-
objc_autoreleasedReturnValue
會檢驗調(diào)用者是否會對該對象執(zhí)行retain
操作,如果會的話就不執(zhí)行autorelease
,直接設(shè)置標(biāo)志符ReturnAtPlus1 -
objc_retainAutoreleaseReturnValue
在檢驗到標(biāo)志符后,也不retain了(后面retain操作),直接返回對象本身,同樣,如果檢測到標(biāo)識符顯示后面沒有retain操作,那么就走一遍retain使其引用計數(shù)加1
所以array這樣的賦值新建一個對象,ARC環(huán)境下引用計數(shù)的1是底層默認(rèn)的返回值加一 沒有調(diào)用retain
其他強(qiáng)引用,才會調(diào)用objc_retain
來使引用計數(shù)加一。objc_retain
是retainAutoreleaseReturnValue
調(diào)用的。
一個問題:為什么要傳入(NSError **)這種類型的參數(shù)
這是一個二級指針(指向指針的指針),將一個基本類型的變量通過函數(shù)參數(shù)傳入函數(shù)內(nèi),在函數(shù)內(nèi)如何改變都不會影響到外部變量的值,那如果我們要在函數(shù)內(nèi)部改變外部變量的值,就應(yīng)該將指針的值傳入函數(shù),然后函數(shù)中根據(jù)指針去找到指向的內(nèi)存進(jìn)行修改。
如果函數(shù)參數(shù)本身是一個對象,我們傳入一個對象,對象本身就是一個地址(但是一個一級指針)。
如以下例子:
#import <Foundation/Foundation.h>
#import "StrongTest.h"
void test(StrongTest *obj) {
obj.name = @"3G Group";
//重新初始化obj,也就是改變參數(shù)obj的值(因為劃分新的內(nèi)存,對象的地址會變,而obj就是對象在內(nèi)存中的地址)
obj = [[StrongTest alloc] init];
obj.name = @"iOS Club";
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
StrongTest *obj = [[StrongTest alloc] init];
obj.name = @"Xi You";
test(obj);
NSLog(@"obj.name = %@", obj.name);
}
return 0;
}
可以看到我們最后的打印結(jié)果中,并沒有打印新初始化的對象的字符串。
這是因為我們使用alloc
init
后系統(tǒng)會在內(nèi)存中新開辟一塊存儲空間存儲一個新的對象,然后將函數(shù)中的obj
存儲的指針值改為這個新的內(nèi)存地址,而函數(shù)外的obj
并沒有發(fā)生改變,還是指向原來的這個對象的地址
如果想在函數(shù)中改變函數(shù)外的對象,就需要用到二級指針,即指向指針的指針。
例子如下:
#import <Foundation/Foundation.h>
#import "StrongTest.h"
void test(StrongTest **obj) {
(*obj).name = @"3G Group";
*obj = [[StrongTest alloc] init];
(*obj).name = @"iOS Club";
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
StrongTest *obj = [[StrongTest alloc] init];
obj.name = @"Xi You";
test(&obj);
NSLog(@"obj.name = %@", obj.name);
}
return 0;
}
可以看到打印的結(jié)果就是我們新初始化后的對象中的字符串。文章來源:http://www.zghlxwxcb.cn/news/detail-576480.html
所以,所以對于NSError **
,我們可以在外面新建一個NSError
,當(dāng)函數(shù)有錯誤時,新建一個NSError
對象并存儲到我們新建的這個NSError
對象中。我們就可以通過判斷NSError
是否為nil
來看函數(shù)運行是否出錯。文章來源地址http://www.zghlxwxcb.cn/news/detail-576480.html
到了這里,關(guān)于【iOS】探索ARC的實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!