ARC 是 iOS 中管理引用計數的技術,幫助 iOS 實現垃圾自動回收,具體實現的原理是由編譯器進行管理的,同時運行時庫協助編譯器輔助完成。主要涉及到 Clang (LLVM 編譯器) 和 objc4 運行時庫。c++
本文主要內容由修飾符 __strong 、 __weak 、 __autorelease 拓展開,分別延伸出引用計數、弱引用表、自動釋放池等實現原理。在閱讀本文以前,你能夠看看下面幾個問題:git
在 ARC 下如何存儲引用計數?github
如[NSDictionary dictionary]
方法建立的對象在 ARC 中有什麼不一樣之處。算法
弱引用表的數據結構。編程
解釋一下自動釋放池中的 Hot Page 和 Cold Page。數組
若是上述幾個問題你已經很是清楚,那本文可能對你的幫助有限,但若是你對這幾個問題還存有疑問,那相信本文必定能解答你的疑問。緩存
在 Objective-C 中,對象的引用關係由引用修飾符來決定,如__strong
、__weak
、__autorelease
等等,編譯器會根據不一樣的修飾符生成不一樣邏輯的代碼來管理內存。bash
首先看看 Clang 在其中具體起到哪些做用,咱們能夠在命令行使用下面的命令來將 Objective-C 代碼轉成 LLVM 中間碼:數據結構
// 切換到你文件路徑下
cd Path
// 利用 main.m 生成中間碼文件 main.ll
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
複製代碼
我在main.m
文件中加入defaultFunction
方法,而後利用的命令行命令將其轉換成中間碼:app
void defaultFunction() {
id obj = [NSObject new];
}
複製代碼
在命令行輸入命令後你能夠在文件夾下面發現main.ll
,它的內容以下:
define void @defaultFunction() #0 {
%1 = alloca i8*, align 8
%2 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
%3 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !8
%4 = bitcast %struct._class_t* %2 to i8*
%5 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %4, i8* %3)
%6 = bitcast i8* %5 to %0*
%7 = bitcast %0* %6 to i8*
store i8* %7, i8** %1, align 8
call void @objc_storeStrong(i8** %1, i8* null) #4
ret void
}
複製代碼
雖然內容有點多,可是仔細分析下來大概就是如下內容:
void defaultFunction() {
id obj = obj_msgSend(NSObject, @selector(new));
objc_storeStrong(obj, null);
}
複製代碼
obj_msgSend(NSObject, @selector(new))
很是好理解,就是新建一個對象,而objc_storeStrong
是 objc4 庫中的方法,具體邏輯以下:
void objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
複製代碼
上面的代碼按順序作了如下 4 件事:
其中objc_retain
和objc_release
也是 objc4 庫中的方法,在本文後面分析 objc4 庫的章節會詳細講。
在分析 ARC 相關源碼以前,須要對 isa 有必定了解,其中存儲了一些很是重要的信息,下面是 isa 的結構組成:
union isa_t
{
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;//->表示使用優化的isa指針
uintptr_t has_assoc : 1;//->是否包含關聯對象
uintptr_t has_cxx_dtor : 1;//->是否設置了析構函數,若是沒有,釋放對象更快
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存儲引用計數將要溢出的時候,藉助Sidetable(散列表)存儲引用計數,has_sidetable_rc設置成1
uintptr_t extra_rc : 19; //->存儲引用計數
};
};
複製代碼
其中nonpointer
、weakly_referenced
、has_sidetable_rc
和extra_rc
都是 ARC 有直接關係的成員變量,其餘的大多也有涉及到。
struct objc_object {
isa_t isa;
};
複製代碼
從下面代碼能夠知道,objc_object
就是 isa 基礎上一層封裝。
struct objc_class : objc_object {
isa_t isa; // 繼承自 objc_object
Class superclass;
cache_t cache; // 方法實現緩存和 vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
};
複製代碼
objc_class
繼承了objc_object
,結構以下:
isa
:objc_object 指向類,objc_class 指向元類。superclass
:指向父類。cache
:存儲用戶消息轉發優化的方法緩存和 vtable 。bits
:class_rw_t 和 class_ro_t ,保存了方法、協議、屬性等列表和一些標誌位。在 MRC 時代 Retain 修飾符將會使被引用的對象引用計數 + 1 ,在 ARC 中 __strong 修飾符做爲其替代者,具體起到什麼樣的做用?咱們能夠經過 Clang 將 Objective-C 代碼轉成 LLVM 來分析其中原理。
接下來繼續將 Objective-C 代碼轉成 LLVM 中間碼,此次咱們試一下__strong
修飾符:
void strongFunction() {
id obj = [NSObject new];
__strong id obj1 = obj;
}
複製代碼
中間碼(後面的中間碼爲了方便理解就刪除無用代碼):
void defaultFunction() {
id obj = obj_msgSend(NSObject, @selector(new));
id obj1 = objc_retain(obj)
objc_storeStrong(obj, null);
objc_storeStrong(obj1, null);
}
複製代碼
上面代碼一看就是很是常規的操做,建立對象、引用計數 + 1 、 分別釋放,將objc_storeStrong
裏面的邏輯嵌入可得:
void defaultFunction() {
id obj = obj_msgSend(NSObject, @selector(new));
id obj1 = objc_retain(obj)
objc_release(obj);
objc_release(obj1);
}
複製代碼
接下來咱們經過分析 objc4 庫的源碼來了解objc_retain
和objc_release
的內部邏輯。先看objc_retain
具體實現:
id objc_retain(id obj) {
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
複製代碼
繼續往下查看最終定位到objc_object::rootRetain
方法:
ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow) {
// 若是是 TaggedPointer 直接返回
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
// 獲取 isa
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
// 未優化的 isa 部分
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
//
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// 正在被釋放的處理
// donot 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;
}
// extra_rc 未溢出時引用計數++
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
// extra_rc 溢出
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
// 從新調用該函數 入參 handleOverflow 爲 true
return rootRetain_overflow(tryRetain);
}
// 保留一半引用計數
// 準備將另外一半複製到 side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
// 更新 isa 值
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// 將另外一半複製到 side table side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
複製代碼
上面的代碼分紅 3 個小分支:
sidetable_retain()
。isa.extra_rc
+ 1 完事。isa.extra_rc
中一半值轉移至sidetable
中,而後將isa.has_sidetable_rc
設置爲true
,表示使用了sidetable
來計算引用次數。繼續看objc_release
具體實現,最終定位到objc_object::rootRelease
方法:
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)) {
// 未優化 isa
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
// 入參是否要執行 Dealloc 函數,若是爲 true 則執行 SEL_dealloc
return sidetable_release(performDealloc);
}
// extra_rc --
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// donot ClearExclusive()
goto underflow;
}
// 更新 isa 值
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
// 處理下溢,從 side table 中借位或者釋放
newisa = oldisa;
// 若是使用了 sidetable_rc
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
// 調用本函數處理下溢
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// 從 sidetable 中借位引用計數給 extra_rc
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
if (borrowed > 0) {
// extra_rc 是計算額外的引用計數,0 即表示被引用一次
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
// 保存失敗,恢復現場,重試
if (!stored) {
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);
}
}
}
// 若是仍是保存失敗,則還回 side table
if (!stored) {
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// 沒有使用 sidetable_rc ,或者 sidetable_rc 計數 == 0 的就直接釋放
// 若是已是釋放中,拋個過分釋放錯誤
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
// 更新 isa 狀態
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
// 執行 SEL_dealloc 事件
__sync_synchronize();
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}
複製代碼
這麼一長串代碼,將其分解後和 rootRetain 邏輯相似:
到這裏能夠知道 引用計數分別保存在isa.extra_rc
和sidetable
中,當isa.extra_rc
溢出時,將一半計數轉移至sidetable
中,而當其下溢時,又會將計數轉回。當兩者都爲空時,會執行釋放流程 。
objc_object::rootRetainCount
方法是用來計算引用計數的。經過前面rootRetain
和rootRelease
的源碼分析能夠看出引用計數會分別存在isa.extra_rc
和sidetable
。中,這一點在rootRetainCount
方法中也獲得了體現。
inline uintptr_t objc_object::rootRetainCount()
{
// TaggedPointer 直接返回
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
// 加載 isa
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
// 優化的 isa 須要 sidetable + bits.extra_rc + 1
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
// 未優化返回 sidetable_retainCount
sidetable_unlock();
return sidetable_retainCount();
}
複製代碼
在 MRC 時代有一句話叫 誰建立誰釋放 ,意思是由開發者經過alloc
、new
、copy
和mutableCopy
等方法建立的對象,須要開發者手動釋放,而由其餘方法建立並返回的對象返回給用戶後也不須要開發者釋放,好比說由[NSMutableArray array]
方法建立的數組,這樣的對象默認由自動釋放池管理。進入 ARC 時代後,針對返回的對象編譯器也作了一些特殊處理,具體經過下面的內容來理解其中奧妙。
首先將下方建立數組的代碼轉成中間碼:
id strongArrayInitFunction() {
return [[NSMutableArray alloc] init];
}
void strongArrayFunction() {
__strong id obj = [NSMutableArray array];
}
複製代碼
中間碼提煉後獲得下面的代碼:
strongArrayInitFunction() {
id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
objc_autoreleaseReturnValue(obj);
return obj;
}
strongArrayFunction() {
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj)
objc_release(obj);
}
複製代碼
相對於用戶建立的對象,[NSMutableArray array]
方法建立返回的對象轉換後多出了一個objc_retainAutoreleasedReturnValue
方法。這涉及到一個最優化處理:
autoreleasePool
的操做,在執行objc_autoreleaseReturnValue
時,根據查看後續調用的方法列表是否包含objc_retainAutoreleasedReturnValue
方法,以此判斷是否走優化流程。objc_autoreleaseReturnValue
時,優化流程將一個標誌位存儲在 TLS (Thread Local Storage) 中後直接返回對象。objc_retainAutoreleasedReturnValue
時檢查 TLS 的標誌位判斷是否處於優化流程,若是處於優化流程中則直接返回對象,而且將 TLS 的狀態還原。下面再經過源代碼來進行分析,先看看objc_autoreleaseReturnValue
具體實現:
id objc_autoreleaseReturnValue(id obj) {
// 若是走優化程序則直接返回對象
if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;
// 不然仍是走自動釋放池
return objc_autorelease(obj);
}
static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) {
assert(getReturnDisposition() == ReturnAtPlus0);
// 檢查使用該函數的方法或調用方的的調用列表,若是緊接着執行 objc_retainAutoreleasedReturnValue ,將不註冊到 autoreleasePool 中
if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
// 設置標記 ReturnAtPlus1
if (disposition) setReturnDisposition(disposition);
return true;
}
return false;
}
// 將 ReturnAtPlus1 或 ReturnAtPlus0 存入 TLS
static ALWAYS_INLINE void setReturnDisposition(ReturnDisposition disposition) {
tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}
// 取出標記
static ALWAYS_INLINE ReturnDisposition getReturnDisposition() {
return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}
複製代碼
objc_autoreleaseReturnValue
代碼邏輯大概分爲:
objc_retainAutoreleasedReturnValue
。下面是objc_retainAutoreleasedReturnValue
的源碼分析:
id objc_retainAutoreleasedReturnValue(id obj) {
// 若是 TLS 中標記表示使用了優化程序,則直接返回
if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
return objc_retain(obj);
}
static ALWAYS_INLINE ReturnDisposition acceptOptimizedReturn() {
// 取出標記後返回
ReturnDisposition disposition = getReturnDisposition();
// 還原至未優化狀態
setReturnDisposition(ReturnAtPlus0); // reset to the unoptimized state
return disposition;
}
複製代碼
objc_retainAutoreleasedReturnValue
代碼邏輯大概分爲:
經過分析源碼能夠得知下面這段代碼的優化流程和未優化流程是有挺大的區別的:
strongArrayFunction() {
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj)
objc_release(obj);
}
複製代碼
最終優化流程至關於:
id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
objc_release(obj);
複製代碼
而未優化流程至關於:
id obj = objc_msgSend(objc_msgSend(NSMutableArray, @selector(alloc)), @selector(init));
objc_autorelease(obj);
objc_retain(obj);
objc_release(obj);
複製代碼
衆所周知,weak 表示弱引用,引用計數不會增長。在原對象釋放後,弱引用變量也會隨之被清除,接下來一步步分析其中原理。
首先將下面代碼轉換成中間碼:
void weakFunction() {
__weak id obj = [NSObject new];
}
void weak1Function() {
id obj = [NSObject new];
__weak id obj1 = obj;
}
void weak2Function() {
id obj = [NSObject new];
__weak id obj1 = obj;
NSLog(@"%@",obj1);
}
複製代碼
下面是轉化提煉後的中間碼:
weakFunction() {
id temp = objc_msgSend(NSObject, @selector(new));
objc_initWeak(&obj, temp);
objc_release(temp);
objc_destroyWeak(obj);
}
weak1Function() {
id obj = objc_msgSend(NSObject, @selector(new));
objc_initWeak(&obj1, obj);
objc_destroyWeak(obj1);
objc_storeStrong(obj, null);
}
weak2Function() {
id obj = objc_msgSend(NSObject, @selector(new));
objc_initWeak(obj1, obj);
id temp = objc_loadWeakRetained(obj1);
NSLog(@"%@",temp);
objc_release(temp);
objc_destroyWeak(obj1);
objc_storeStrong(obj, null);
}
複製代碼
weakFunction: 在該方法中聲明 __weak 對象後並無使用到,因此在objc_initWeak
後,當即釋放調用了objc_release
和objc_destroyWeak
方法。
weak1Function:該方法中obj
是強引用,obj1
是弱引用,objc_initWeak
、 objc_destroyWeak
前後成對調用,對應着弱引用變量的初始化和釋放方法。
weak2Function:和weak1Function
不一樣之處是使用了弱引用變量obj1
,在使用弱引用變量以前,編譯器建立了一個臨時的強引用對象,在用完後當即釋放。
下面是objc_initWeak
和objc_destroyWeak
的代碼實現:
id objc_initWeak(id *location, id newObj) {
if (!newObj) {
*location = nil;
return nil;
}
// 該地址沒有值,正賦予新值,若是正在釋放將會 crash
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
void objc_destroyWeak(id *location) {
// 該地址有值,沒有賦予新值,若是正在釋放不 crash
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
複製代碼
經過源代碼能夠發現最終都是經過storeWeak
來實現各自邏輯的,在查看storeWeak
實現以前,咱們要先了解一下它的模板參數的含義:
storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj);
複製代碼
其中DontHaveOld
、DoHaveNew
和DoCrashIfDeallocating
都是模板參數,具體含義以下:
enum HaveOld { DontHaveOld = false, DoHaveOld = true }; // 是否有值
enum HaveNew { DontHaveNew = false, DoHaveNew = true }; // 是否有新值
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
}; // 操做正在釋放中的對象是否 Crash
複製代碼
接下來繼續看storeWeak
的實現:
template <HaveOld haveOld, HaveNew haveNew,
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;
retry:
// 從 SideTables 中取出存儲弱引用表的 SideTable(爲弱引用表 weak_table_t 的一層封裝)
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// location 指向的值發生改變,則從新執行獲取 oldObj
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// 若是有新值
if (haveNew && newObj) {
// 若是該對象類還未初始化則進行初始化
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
// 建立一個非元類,而且初始化,會調用 +initialize 函數
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
previouslyInitializedClass = cls;
goto retry;
}
}
// 若是有舊值,清除舊值對應的弱引用表
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// 若是賦予了新值,註冊新值對應的弱引用表
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// 設置 isa 標誌位 weakly_referenced 爲 true
if (newObj && !newObj->isTaggedPointer()) {
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);
return (id)newObj;
}
複製代碼
這段代碼大概作了這幾件事:
SideTables
中,利用對象自己地址進行位運算後獲得對應下標,取得該對象的弱引用表。SideTables
是一個 64 個元素長度的散列表,發生碰撞時,可能一個SideTable
中存在多個對象共享一個弱引用表。isa.weakly_referenced
設置爲true
,表示該對象是有弱引用變量,釋放時要去清空弱引用表。在上面的代碼中使用到weak_register_no_lock
和weak_unregister_no_lock
來進行弱引用表的註冊和註銷,繼續查看這兩個方法的實現:
id weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id; // 被引用的對象
objc_object **referrer = (objc_object **)referrer_id; // 弱引用變量
if (!referent || referent->isTaggedPointer()) return referent_id;
// 檢查當前對象沒有在釋放中
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
// 若是正在釋放中,則根據 crashIfDeallocating 判斷是否觸發 crash
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
weak_entry_t *entry; // 每一個對象對應的一個弱引用記錄
// 若是當前表中有該對象的記錄則直接加入該 weak 表中對應記錄
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
// 沒有在 weak 表中找到對應記錄,則新建一個記錄
weak_entry_t new_entry(referent, referrer);
// 查看 weak_table 表是否要擴容
weak_grow_maybe(weak_table);
// 將記錄插入 weak 表中
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
複製代碼
上面這段代碼主要邏輯:
crashIfDeallocating
判斷是否觸發 crash 。weak_table
中是否有被引用對象對應的entry
,若是有則直接將弱引用變量指針地址加入該entry
中。weak_table
沒有找到對應的entry
,則新建一個entry
,並將弱引用變量指針地址加入entry
中。同時檢查weak_table
是否須要擴容。下面是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;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 找到 weak 表中對應記錄後,將引用從記錄中移除
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) {
weak_entry_remove(weak_table, entry);
}
}
}
複製代碼
上面這段代碼主要邏輯:
weak_table
中根據找到被引用對象對應的entry
,而後將弱引用變量指針referrer
從entry
中移除。referrer
以後,檢查entry
是否爲空,若是爲空將其從weak_table
中移除。上面的代碼中使用weak_table
保存被引用對象的entry
,下面繼續經過分析weak_table
的增刪查函數的具體實現:
static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
// hash_pointer 對地址作位運算得出哈希表下標的方式
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
// 線性探測,若是該下標存儲的是其餘對象,那往下移,直至找到正確的下標。
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask; // 不能超過 weak_table 最大長度限制
// 回到初始下標,異常報錯
if (index == begin) bad_weak_table(weak_table->weak_entries);
// 每次衝突下移 hash_displacement + 1,當前位移不超過記錄在案的最大位移
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
複製代碼
上述代碼就是weak_table
查找entry
的過程,也是哈希表尋址過程,使用線性探測的方法解決哈希衝突的問題:
weak_table
最大長度,同時hash_displacement
不能超過記錄的max_hash_displacement
最大哈希位移。max_hash_displacement
是全部插入操做時記錄的最大哈希位移,若是超過了,那確定是出錯了。下面是weak_table
插入entry
的代碼實現:
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
weak_entry_t *weak_entries = weak_table->weak_entries;
assert(weak_entries != nil);
// 經過哈希算法獲得下標
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0;
// 判斷當前下標是否爲空,若是不是繼續往下尋址空位
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_entries);
hash_displacement++;
}
// 找到空位後存入
weak_entries[index] = *new_entry;
weak_table->num_entries++;
// 更新最大哈希位移值
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}
複製代碼
和查過過程相似,weak_table
插入entry
的的步驟:
weak_table
的當前成員數量num_entries
和最大哈希位移max_hash_displacement
。下面是weak_table
移除entry
的代碼實現:
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// 釋放 entry 中的全部弱引用
if (entry->out_of_line()) free(entry->referrers);
// 置空指針
bzero(entry, sizeof(*entry));
// 更新 weak_table 對象數量,並檢查是否能夠縮減表容量
weak_table->num_entries--;
weak_compact_maybe(weak_table);
}
複製代碼
從weak_table
移除entry
的的步驟:
entry
和其中的弱引用變量。在弱引用表中entry
對應着被引用的對象,而referrer
表明弱引用變量。每次被弱引用時,都會將弱引用變量指針referrer
加入entry
中,而當原對象被釋放時,會將entry
清空並移除。
下面看往entry
中添加referrer
的具體實現:
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
// inline_referrers 未超出時,直接加入 inline_referrers 中
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// 若是 inline_referrers 超出 WEAK_INLINE_COUNT 數量,則執行下面代碼
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
// 將 inline_referrers 的引用轉移只 new_referrers
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i];
}
// 修改 entry 內容及標誌位
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
entry->mask = WEAK_INLINE_COUNT-1;
entry->max_hash_displacement = 0;
}
assert(entry->out_of_line());
// 當負載因子太高進行擴容
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
// 根據地址計算下標
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
// 該下表位置下不爲空,發生 hash 碰撞了,
while (entry->referrers[index] != nil) {
// 後移
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
// 記錄最大位移
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
// 找到合適下標後存儲
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
複製代碼
entry
的結構和weak_table
類似,都使用了哈希表,而且使用線性探測法尋找對應位置。在此基礎上有一點不一樣的地方:
entry
有一個標誌位out_of_line
,最初時該標誌位爲false
,entry
使用的是一個有序數組inline_referrers
的存儲結構。inline_referrers
的成員數量超過了WEAK_INLINE_COUNT
,out_of_line
標誌位變成true
,開始使用哈希表存儲結構。每當哈希表負載超過 3/4 時會進行擴容。繼續看從entry
移除referrer
的具體實現:
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line()) {
// 未超出 inline_referrers 時直接將對應位置清空
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
// 超出 inline_referrers 的邏輯
// 根據地址計算下標
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
// 發生哈希衝突繼續日後查找
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
hash_displacement++;
if (hash_displacement > entry->max_hash_displacement) {
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
}
// 找到後將對應位置置空
entry->referrers[index] = nil;
entry->num_refs--;
}
複製代碼
從entry
移除referrer
的步驟:
out_of_line
爲false
時,從有序數組inline_referrers
中查找並移除。out_of_line
爲true
時,從哈希表中查找並移除。當被引用的對象被釋放後,會去檢查isa.weakly_referenced
標誌位,每一個被弱引用的對象weakly_referenced
標誌位都爲true
。
- (void)dealloc {
_objc_rootDealloc(self);
}
複製代碼
順着dealloc
方法邏輯往下走直至clearDeallocating_slow
:
NEVER_INLINE void objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
// 根據指針獲取對應 weak_table
SideTable& table = SideTables()[this];
table.lock();
// 判斷若是有被弱引用則清空該對象對應的 entry
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
// 清空該對象存儲在 sidetable 中的引用計數
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
複製代碼
從上面的代碼能夠看出,在對象釋執行dealloc
函數時,會檢查isa.weakly_referenced
標誌位,而後判斷是否要清理weak_table
中的entry
。
經過前面的中間碼分析能夠得知,在使用弱引用變量以前,編譯器建立了一個臨時的強引用對象,以此保證使用時不會由於被釋放致使出錯,在用完後當即釋放。
下面看看如何對弱引用指針指向對象進行強引用:
id objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// 獲得弱引用指針指向對象
obj = *location;
if (!obj) return nil;
if (obj->isTaggedPointer()) return obj; // TaggedPointer 直接返回
// 獲得對應 weak_table
table = &SideTables()[obj];
// 若是被引用對象在此期間發生變化則重試
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
// 類和超類沒有自定義 retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference 等方法
assert(cls->isInitialized());
// 嘗試 retain
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
// 獲取自定義 SEL_retainWeakReference 方法
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
class_getMethodImplementation(cls, SEL_retainWeakReference);
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
// 調用自定義函數
else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
result = nil;
}
}
else {
// 類未初始化,則初始化後回到 retry 從新執行
table->unlock();
_class_initialize(cls);
goto retry;
}
}
table->unlock();
return result;
}
複製代碼
上面的代碼主要邏輯:
retain
方法,若是沒有,則使用默認rootTryRetain
方法,使引用計數 + 1 。retain
方法,則調用自定義方法,在調用以前會先判斷該對象所屬類是否已經初始化過,若是沒有初始化會先進行初始化而後再調用。在 ARC 環境下, __autorelease 修飾符能夠將對象加入自動釋放池中,由自動釋放池管理釋放。
將下面的代碼轉換成中間碼查看編譯器的處理:
void autoReleasingFunction() {
@autoreleasepool {
__autoreleasing id obj = [NSObject new];
}
}
複製代碼
轉換後的中間碼:
void autoReleasingFunction() {
id token = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(new));
objc_autorelease(obj);
objc_autoreleasePoolPop(token);
}
複製代碼
經過上面的代碼能夠分析出:
@autoreleasepool{}
關鍵字經過編譯器轉換成objc_autoreleasePoolPush
和objc_autoreleasePoolPop
這一對方法。objc_autorelease
,將obj
加入自動釋放池中。從上面的分析能夠看出,編譯器對自動釋放池的處理邏輯大體分紅:
objc_autoreleasePoolPush
做爲自動釋放池做用域的第一個函數。objc_autorelease
將對象加入自動釋放池。objc_autoreleasePoolPop
做爲自動釋放池做用域的最後一個函數。在繼續往下看以前,咱們須要先了解一些關於自動釋放池的相關知識:
自動釋放池都是由一個或者多個AutoreleasePoolPage
組成,page
的 SIZE 爲 4096 bytes ,它們經過parent
和child
指針組成一個雙向鏈表。
hotPage
:是當前正在使用的page
,操做都是在hotPage
上完成,通常處於鏈表末端或者倒數第二個位置。存儲在 TLS 中,能夠理解爲一個每一個線程共享一個自動釋放池鏈表。
coldPage
:位於鏈表頭部的page
,可能同時爲hotPage
。
POOL_BOUNDARY
:nil
的宏定義,替代以前的哨兵對象POOL_SENTINEL
,在自動釋放池建立時,在objc_autoreleasePoolPush
中將其推入自動釋放池中。在調用objc_autoreleasePoolPop
時,會將池中對象按順序釋放,直至遇到最近一個POOL_BOUNDARY
時中止。
EMPTY_POOL_PLACEHOLDER
:當自動釋放池中沒有推入過任何對象時,這個時候推入一個POOL_BOUNDARY
,會先將EMPTY_POOL_PLACEHOLDER
存儲在 TLS 中做爲標識符,而且這次並不推入POOL_BOUNDARY
。等再次有對象被推入自動釋放池時,檢查在 TLS 中取出該標識符,這個時候再推入POOL_BOUNDARY
。
next
:指向AutoreleasePoolPage
指向棧頂空位的指針,每次加入新的元素都會往上移動。
objc_autoreleasePoolPush
方法的代碼實現:
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {
// 測試狀態下,每一個自動釋放池都會新建一個 page
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
// 推一個 POOL_BOUNDARY 入棧,表示該釋放池的起點
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
複製代碼
objc_autoreleasePoolPush
方法其實就是向自動釋放池推入一個POOL_BOUNDARY
,做爲該autoreleasepool
的起點。autoreleaseFast
方法的具體邏輯將在後面分析autorelease
方法時再進行分析。
下面看加入自動釋放池方法autorelease
的代碼實現:
static inline id autorelease(id obj) {
id *dest __unused = autoreleaseFast(obj);
return obj;
}
複製代碼
繼續往下看:
static inline id *autoreleaseFast(id obj) {
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
// 有 hotPage 且不滿 直接加入棧中
return page->add(obj);
} else if (page) {
// hotPage 已滿 先建立一個 Page 後,加入新 Page 中
return autoreleaseFullPage(obj, page);
} else {
// 沒有 hotPage 直接新建一個 Page,並加入 Page 中
return autoreleaseNoPage(obj);
}
}
複製代碼
上面這段代碼邏輯:
hotPage
存在且未滿,則直接推入hotPage
。hotPage
存在且已滿,調用autoreleaseFullPage
。hotPage
不存在,調用autoreleaseNoPage
。往下繼續分析autoreleaseFullPage
的實現:
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
assert(page == hotPage());
assert(page->full() || DebugPoolAllocation);
// 找到一個未滿的 page , 未找到則新建一個 page ,設置成 hotPage
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
複製代碼
該方法是在hotPage
已滿的狀況下執行,具體邏輯以下:
hotPage
是否有後繼節點,若是有直接使用後繼節點。AutoreleasePoolPage
。page
,並將其設置爲hotPage
,其實就是存入 TLS 中共享。下面開始分析autoreleaseNoPage
方法代碼實現:
id *autoreleaseNoPage(id obj)
{
// 執行 No page 表示目前尚未釋放池,或者有一個空佔位符池,可是尚未加入對象
assert(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
// 若是是空佔位符池,須要加入一個釋放池邊界
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
// 若是傳入 POOL_BOUNDARY 則設置空池佔位符
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
return setEmptyPoolPlaceholder();
}
// We are pushing an object or a non-placeholder d pool.
// 初始化一個 page 並設置 hotPage
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// 插入釋放池邊界
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// 對象加入釋放池
return page->add(obj);
}
複製代碼
autoreleaseNoPage
只有在自動釋放池尚未page
時調用,主要邏輯:
POOL_BOUNDARY
時,將EmptyPoolPlaceholder
存入 TLS 中。EmptyPoolPlaceholder
時,在建立好page
以後,會先推入一個POOL_BOUNDARY
,而後再將加入自動釋放池的對象推入。在自動釋放池所在做用域結束時,會調用objc_autoreleasePoolPop
,對自動釋放池中的對象進行釋放。
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
// 若是是空池佔位符,要清空整個自動釋放池
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
if (hotPage()) {
// 若是存在 hotPage ,則找到 coldPage 的起點 從新 pop
pop(coldPage()->begin());
} else {
// 未使用過的釋放池,置空 TLS 中存放的 hotPage
setHotPage(nil);
}
return;
}
page = pageForPointer(token);
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
// 在 stop 不爲 POOL_BOUNDARY 的狀況下 只多是 coldPage()->begin()
if (stop == page->begin() && !page->parent) {
} else {
// 若是不是 POOL_BOUNDARY 也不是 coldPage()->begin() 則報錯
return badPop(token);
}
}
if (PrintPoolHiwat) printHiwat();
// 釋放 stop 後面的全部對象
page->releaseUntil(stop);
// 清除後續節點 page
if (page->child) {
// 若是當前 page 沒有達到半滿,則幹掉全部後續 page
if (page->lessThanHalfFull()) {
page->child->kill();
}
// 若是當前 page 達到半滿以上,則保留下一頁
else if (page->child->child) {
page->child->child->kill();
}
}
}
複製代碼
上面這段代碼邏輯:
EMPTY_POOL_PLACEHOLDER
,若是是則繼續判斷是否hotPage
存在,若是hotPage
存在則將釋放的終點改爲coldPage()->begin()
,若是hotPage
不存在,則置空 TLS 存儲中的hotPage
。stop
既不是POOL_BOUNDARY
也不是coldPage()->begin()
的狀況將報錯。stop
以後的全部對象。page
若是沒有達到半滿,則幹掉全部後續全部 page,若是超過半滿則只保留下一個page
。整個AutoreleasePoolPage
就是一個堆棧,經過AutoreleasePoolPage
的add
方法將對象加入自動釋放池:
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // 複製指針
*next++ = obj; // 將 obj 存入 next 指向的內存地址後,next 向後移指向下一個空地址
protect();
return ret;
}
複製代碼
這段邏輯很是簡單,add
方法將新加入的對象存入棧頂指針next
指向的地址中,而後指向下一個位置。
下面是AutoreleasePoolPage
出棧的實現:
void releaseUntil(id *stop)
{
// 當 next 和 stop 不是指向同一塊內存地址,則繼續執行
while (this->next != stop) {
AutoreleasePoolPage *page = hotPage();
// 若是 hotPage 空了,則設置上一個 page 爲 hotPage
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next; // page->next-- 後指向當前棧頂元素
memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); // 將棧頂元素內存清空
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj); // 棧頂元素引用計數 - 1
}
}
setHotPage(this);
}
複製代碼
這段代碼大體邏輯:
next
和stop
不是指向同一塊內存地址時,繼續出棧。page
若是被清空,則繼續清理鏈表中的上一個page
。POOL_BOUNDARY
,則調用objc_release
引用計數 - 1 。經過閱讀 objc4 源碼,將之前關於 ARC 的知識串聯起來,其中對細節的實現原理理解得更加透徹。
若是你以爲本文還不錯的話,能夠到原文 【理解 ARC 實現原理】 給個 ✨ 。