前言
本文的ARC特指Objective C的ARC,並不會講解其他語言。另外,本文涉及到的原理部分較多,適合有一定經驗的開發者。
什麼是ARC?
ARC的全稱Auto Reference Counting. 也就是自動引用計數。那麼,為什麼要有ARC呢?
我們從C語言開始。使用C語言編程的時候,如果要在堆上分配一塊內存,代碼如下
//分配內存(malloc/calloc均可) int * array = calloc(10, sizeof (int)); //釋放內存 free(array);1234512345
C是面向過程的語言(Procedural programming),這種內存的管理方式簡單直接。但是,對於面向對象編程,這種手動的分配釋放毫無疑問會大大的增加代碼的復雜度。
於是,OOP的語言引入了各種各樣的內存管理方法,比如Java的垃圾回收和Objective C的引用計數。關於垃圾回收和飲用計數的對比,可以參見Brad Larson的這個SO回答。
Objective C的引用計數理解起來很容易,當一個對象被持有的時候計數加一,不再被持有的時候引用計數減一,當引用計數為零的時候,說明這個對象已經無用了,則將其釋放。
引用計數分為兩種:
手動引用計數(MRC)
自動引用計數(ARC)
在iOS開發早期,編寫代碼是采用MRC的
// MRC代碼 NSObject * obj = [[NSObject alloc] init]; //引用計數為1 //不需要的時候 [obj release] //引用計數減1 //持有這個對象 [obj retain] //引用計數加1 //放到AutoReleasePool [obj autorelease]//在auto release pool釋放的時候,引用計數減1
雖說這種方式提供了面向對象的內存管理接口,但是開發者不得不花大量的時間在內存管理上,並且容易出現內存洩漏或者release一個已被釋放的對象,導致crash。
再後來,Apple對iOS/Mac OS開發引入了ARC。使用ARC,開發者不再需要手動的retain/release/autorelease. 編譯器會自動插入對應的代碼,再結合Objective C的runtime,實現自動引用計數。
比如如下ARC代碼:
NSObject * obj; { obj = [[NSObject alloc] init]; //引用計數為1 } NSLog(@"%@",obj);
等同於如下MRC代碼
NSObject * obj; { obj = [[NSObject alloc] init]; //引用計數為1 [obj relrease] } NSLog(@"%@",obj);
在Objective C中,有三種類型是ARC適用的:
block
objective 對象,id, Class, NSError*等
由attribute((NSObject))標記的類型。
像double *,CFStringRef等不是ARC適用的,仍然需要手動管理內存。
Tips: 以CF開頭的(Core Foundation)的對象往往需要手動管理內存。
屬性所有權
最後,我們在看看ARC中常見的所有權關鍵字,
assign對應關鍵字__unsafe_unretained, 顧名思義,就是指向的對象被釋放的時候,仍然指向之前的地址,容易引起野指針。
copy對應關鍵字__strong,只不過在賦值的時候,調用copy方法。
retain對應__strong
strong對應__strong
unsafe_unretained對應__unsafe_unretained
weak對應__weak。
其中,__weak和__strong是本文要講解的核心內容。
ARC的內部實現
ARC背後的引用計數主要依賴於這三個方法:
retain 增加引用計數
release 降低引用計數,引用計數為0的時候,釋放對象。
autorelease 在當前的auto release pool結束後,降低引用計數。
在Cocoa Touch中,NSObject協議中定義了這三個方法,由於Cocoa Touch中,絕大部分類都繼承自NSObject(NSObject類本身實現了NSObject協議),所以可以“免費”獲得NSObject提供的運行時和ARC管理方法,這就是為什麼適用OC開發iOS的時候,你的類要繼承自NSObject。
既然ARC是引用計數,那麼對應一個對象,內存中必然會有一個地方來存儲這個對象的引用計數。iOS的Runtime是開源的,在這裡可以下載到全部的代碼,我們通過源代碼一探究竟。
我們從retain入手,
- (id)retain { return ((id)self)->rootRetain(); } inline id objc_object::rootRetain() { if (isTaggedPointer()) return (id)this; return sidetable_retain(); }
所以說,本質上retain就是調用sidetable_retain,再看看sitetable_retain的實現:
id objc_object::sidetable_retain() { //獲取table SideTable& table = SideTables()[this]; //加鎖 table.lock(); //獲取引用計數 size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { //增加引用計數 refcntStorage += SIDE_TABLE_RC_ONE; } //解鎖 table.unlock(); return (id)this; }
到這裡,retain如何實現就很清楚了,通過SideTable這個數據結構來存儲引用計數。我們看看這個數據結構的實現:
可以看到,這個數據結構就是存儲了一個自旋鎖,一個引用計數map。這個引用計數的map以對象的地址作為key,引用計數作為value。到這裡,引用計數的底層實現我們就很清楚了。
存在全局的map,這個map以地址作為key,引用計數的值作為value。
再來看看release的實現:
SideTable& table = SideTables()[this]; bool do_dealloc = false; table.lock(); //找到對應地址的 RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end()) { //找不到的話,執行dellloc do_dealloc = true; table.refcnts[this] = SIDE_TABLE_DEALLOCATING; } else if (it->second < SIDE_TABLE_DEALLOCATING) {//引用計數小於阈值,dealloc do_dealloc = true; it->second |= SIDE_TABLE_DEALLOCATING; } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { //引用計數減去1 it->second -= SIDE_TABLE_RC_ONE; } table.unlock(); if (do_dealloc && performDealloc) { //執行dealloc ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return do_dealloc;
release的到這裡也比較清楚了:查找map,對引用計數減1,如果引用計數小於阈值,則調用SEL_dealloc
Autorelease pool
上文提到了,autorelease方法的作用是把對象放到autorelease pool中,到pool drain的時候,會釋放池中的對象。舉個例子
__weak NSObject * obj; NSObject * temp = [[NSObject alloc] init]; obj = temp; NSLog(@"%@",obj); //非空
放到auto release pool中,
__weak NSObject * obj; @autoreleasepool { NSObject * temp = [[NSObject alloc] init]; obj = temp; } NSLog(@"%@",obj); //null
可以看到,放到自動釋放池的對象是在超出自動釋放池作用域後立即釋放的。事實上在iOS 程序啟動之後,主線程會啟動一個Runloop,這個Runloop在每一次循環是被自動釋放池包裹的,在合適的時候對池子進行清空。
對於Cocoa框架來說,提供了兩種方式來把對象顯式的放入AutoReleasePool.
NSAutoreleasePool(只能在MRC下使用)
@autoreleasepool {}代碼塊(ARC和MRC下均可以使用)
那麼AutoRelease pool又是如何實現的呢?
我們先從autorelease方法源碼入手
//autorelease方法 - (id)autorelease { return ((id)self)->rootAutorelease(); } //rootAutorelease 方法 inline id objc_object::rootAutorelease() { if (isTaggedPointer()) return (id)this; //檢查是否可以優化 if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; //放到auto release pool中。 return rootAutorelease2(); } // rootAutorelease2 id objc_object::rootAutorelease2() { assert(!isTaggedPointer()); return AutoreleasePoolPage::autorelease((id)this); }
可以看到,把一個對象放到auto release pool中,是調用了AutoreleasePoolPage::autorelease這個方法。
我們繼續查看對應的實現:
public: static inline id autorelease(id obj) { assert(obj); assert(!obj->isTaggedPointer()); id *dest __unused = autoreleaseFast(obj); assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); return obj; } static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); if (page && !page->full()) { return page->add(obj); } else if (page) { return autoreleaseFullPage(obj, page); } else { return autoreleaseNoPage(obj); } } id *add(id obj) { assert(!full()); unprotect(); id *ret = next; // faster than `return next-1` because of aliasing *next++ = obj; protect(); return ret; }
到這裡,autorelease方法的實現就比較清楚了,
autorelease方法會把對象存儲到AutoreleasePoolPage的鏈表裡。等到auto release pool被釋放的時候,把鏈表內存儲的對象刪除。所以,AutoreleasePoolPage就是自動釋放池的內部實現。
__weak與__strong
用過block的同學一定寫過類似的代碼:
__weak typeSelf(self) weakSelf = self; [object fetchSomeFromRemote:^{ __strong typeSelf(weakSelf) strongSelf = weakSelf; //從這裡開始用strongSelf }];
那麼,為什麼要這麼用呢?原因是:
block會捕獲外部變量,用weakSelf保證self不會被block被捕獲,防止引起循環引用或者不必要的額外生命周期。
用strongSelf則保證在block的執行過程中,對象不會被釋放掉。
首先__strong和__weak都是關鍵字,是給編譯器理解的。為了理解其原理,我們需要查看它們編譯後的代碼,使用XCode,我們可以容易的獲得一個文件的匯編代碼。
比如,對於Test.m文件,當源代碼如下時:
#import "Test.h" @implementation Test - (void)testFunction{ { __strong NSObject * temp = [[NSObject alloc] init]; } } @end
轉換後的匯編代碼如下:
Ltmp3: .loc 2 15 37 prologue_end ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:37 ldr x9, [x9] ldr x1, [x8] mov x0, x9 bl _objc_msgSend adrp x8, L_OBJC_SELECTOR_REFERENCES_.2@PAGE add x8, x8, L_OBJC_SELECTOR_REFERENCES_.2@PAGEOFF .loc 2 15 36 is_stmt 0 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:36 ldr x1, [x8] .loc 2 15 36 discriminator 1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:36 bl _objc_msgSend mov x8, #0 add x9, sp, #8 ; =8 .loc 2 15 29 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:29 str x0, [sp, #8] Ltmp4: .loc 2 16 5 is_stmt 1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:16:5 mov x0, x9 mov x1, x8 bl _objc_storeStrong .loc 2 17 1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:17:1 ldp x29, x30, [sp, #32] ; 8-byte Folded Reload add sp, sp, #48 ; =48 ret Ltmp5:
即使你不懂匯編,也能很輕易的獲取到調用順序如下
_objc_msgSend // alloc _objc_msgSend // init _objc_storeStrong // 強引用
在結合Runtime的源碼,我們看看最關鍵的objc_storeStrong的實現
void objc_storeStrong(id *location, id obj) { id prev = *location; if (obj == prev) { return; } objc_retain(obj); *location = obj; objc_release(prev); } id objc_retain(id obj) { return [obj retain]; } void objc_release(id obj) { [obj release]; }
我們再來看看__weak. 將Test.m修改成為如下代碼,同樣我們分析其匯編實現
.loc 2 15 35 prologue_end ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:35 ldr x9, [x9] ldr x1, [x8] mov x0, x9 bl _objc_msgSend adrp x8, L_OBJC_SELECTOR_REFERENCES_.2@PAGE add x8, x8, L_OBJC_SELECTOR_REFERENCES_.2@PAGEOFF .loc 2 15 34 is_stmt 0 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:34 ldr x1, [x8] .loc 2 15 34 discriminator 1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:34 bl _objc_msgSend add x8, sp, #24 ; =24 .loc 2 15 27 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:27 mov x1, x0 .loc 2 15 27 discriminator 2 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:27 str x0, [sp, #16] ; 8-byte Folded Spill mov x0, x8 bl _objc_initWeak .loc 2 15 27 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:27 ldr x1, [sp, #16] ; 8-byte Folded Reload .loc 2 15 27 discriminator 3 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:27 str x0, [sp, #8] ; 8-byte Folded Spill mov x0, x1 bl _objc_release add x8, sp, #24 Ltmp4: .loc 2 16 5 is_stmt 1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:16:5 mov x0, x8 bl _objc_destroyWeak .loc 2 17 1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:17:1 ldp x29, x30, [sp, #48] ; 8-byte Folded Reload add sp, sp, #64 ; =64 ret
可以看到,__weak本身實現的核心就是以下兩個方法
_objc_initWeak
_objc_destroyWeak
我們通過Runtime的源碼分析這兩個方法的實現:
id objc_initWeak(id *location, id newObj) { //省略.... return storeWeak (location, (objc_object*)newObj); } void objc_destroyWeak(id *location) { (void)storeWeak (location, nil); }
所以,本質上都是調用了storeWeak函數,這個函數內容較多,主要做了以下事情
獲取存儲weak對象的map,這個map的key是對象的地址,value是weak引用的地址。
當對象被釋放的時候,根據對象的地址可以找到對應的weak引用的地址,將其置為nil即可。
這就是在weak背後的黑魔法。
總結
這篇文章屬於想到哪裡寫到哪裡的類型,後邊有時間了在繼續總結ARC的東西吧。