引言:
我們都知道Objective-C通過“引用計數”來管理物件釋放。基本原理就是管理物件的持有者個數(引用計數),引用計數為0時釋放物件。現在有ARC(自動引用計數),則無需我們自己顯式持有(retain)和釋放(release)物件,ARC通過對對像加上所有權修飾符(__strong等),編譯器通過物件的所有權修飾符將會自動鍵入引用計數管理(根據所有權修飾符自動鍵入retain、release、autorelease)
本文主要敘述引用計數的實現原理,ARC和MRC在使用上的區別,以及編譯器在ARC中為我們做了什麼。
Objective-C物件的MRC
- retain
- release
- autorelease
autorelease的實現
使用棧(後進先出)來管理NSAutoreleasePool物件,因此可以隨時拿到最近(hotPage)的NSAutoreleasePool,呼叫物件的autorelease方法,會在hotPage中的內部陣列將物件加入進去。當NSAutoreleasePool出棧時,呼叫內部陣列中元素的release方法即可。
retain/release的實現
引用計數表:使用雜湊表實現引用計數表,key為物件的地址的雜湊值,value為引用計數和記憶體塊地址。
retain:通過物件的地址在引用計數表中找到引用計數,如果retainCount超過最大值,則拋異常,否則retainCount加1。
release:通過物件的地址在引用計數表中找到引用計數,如果retainCount值為0,則拋異常。否則retainCount減1,如果retainCount減1後為0,則從引用計數表衝移除,並呼叫物件的dealloc方法。
Objective-C物件的ARC
Objective-C物件的ARC是通過所有權修飾符來管理物件的持有和釋放。所有權修飾符一共有4種:
- __strong 修飾符,預設的修飾符
- __weak 修飾符
- __unsafe_unretained 修飾符
- __autoreleasing 修飾符
__strong修飾符的實現
取得自己生成並且持有物件:
使用ARC:
{
id obj1 = [NSObject new];
//相當於
//id __strong obj1 = [NSObject new];
}複製程式碼
不使用ARC:
{
id obj1 = [NSObject new];
//在變數作用域結束時插入release
[obj1 release];
}複製程式碼
取得非自己生成並持有物件:
使用ARC:
- (void)create {
test = [self object];
NSLog(@"ARC------------------ARC");
NSLog(@"after create count = %ld", _objc_rootRetainCount(test));
}
- (id)object {
id obj = [[MyObject alloc] init];
return obj;
}
- (void)printRetainCount {
NSLog(@"ARC------------------ARC");
NSLog(@"retain count = %ld", _objc_rootRetainCount(test));
}複製程式碼
不使用ARC:
- (void)create {
if (test) {
NSLog(@"MRC------------------MRC");
NSLog(@"release");
[test release];
}
test = [self object];
[test retain];
NSLog(@"MRC------------------MRC");
NSLog(@"after create count = %ld", [test retainCount]);
}
- (id)object {
id obj = [[MyObject alloc] init];
[obj autorelease];
return obj;
}
- (void)printRetainCount {
NSLog(@"MRC------------------MRC");
NSLog(@"retain count = %ld", [test retainCount]);
}複製程式碼
列印結果:
**2016-12-13 15:35:47.545 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:47.545 ARCLearn[53676:16388070] after create count = 1**
**2016-12-13 15:35:47.546 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:47.546 ARCLearn[53676:16388070] after create count = 2**
**2016-12-13 15:35:49.602 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:49.602 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:35:49.603 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:49.603 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:35:51.212 ARCLearn[53676:16388070] myobject dealloc**
**2016-12-13 15:35:51.213 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] after create count = 1**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] release**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] myobject dealloc**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] after create count = 2**
**2016-12-13 15:36:03.540 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:36:03.542 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:36:03.542 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:36:03.543 ARCLearn[53676:16388070] retain count = 1**複製程式碼
可以看到使用ARC的物件持有,在返回物件時,並沒有把物件註冊到autoreleasepool中,下面為ARC的autoreleasepool列印:
**objc[54013]: ##############**
**objc[54013]: AUTORELEASE POOLS for thread 0x10c6ba3c0**
**objc[54013]: 13 releases pending.**
**objc[54013]: [0x7fa3c9811000] ................ PAGE (hot) (cold)**
**objc[54013]: [0x7fa3c9811038] ################ POOL 0x7fa3c9811038**
**objc[54013]: [0x7fa3c9811040] 0x60800002e3c0 __NSCFString**
**objc[54013]: [0x7fa3c9811048] ################ POOL 0x7fa3c9811048**
**objc[54013]: [0x7fa3c9811050] 0x7fa3ca800850 UIScreen**
**objc[54013]: [0x7fa3c9811058] 0x7fa3ca800850 UIScreen**
**objc[54013]: [0x7fa3c9811060] 0x6000002712c0 __NSCFDictionary**
**objc[54013]: [0x7fa3c9811068] 0x7fa3c8600bf0 UIWindow**
**objc[54013]: [0x7fa3c9811070] 0x60000008e6f0 __NSMallocBlock__**
**objc[54013]: [0x7fa3c9811078] 0x6000000567d0 __NSSetM**
**objc[54013]: [0x7fa3c9811080] 0x600000052de0 __NSSetM**
**objc[54013]: [0x7fa3c9811088] 0x600000053b30 __NSSetM**
**objc[54013]: [0x7fa3c9811090] 0x600000271640 __NSCFString**
**objc[54013]: [0x7fa3c9811098] 0x600000271640 __NSCFString**
**objc[54013]: ##############**複製程式碼
下面為MRC的autoreleasepool列印:
**objc[54013]: ##############**
**objc[54013]: AUTORELEASE POOLS for thread 0x10c6ba3c0**
**objc[54013]: 14 releases pending.**
**objc[54013]: [0x7fa3c9811000] ................ PAGE (hot) (cold)**
**objc[54013]: [0x7fa3c9811038] ################ POOL 0x7fa3c9811038**
**objc[54013]: [0x7fa3c9811040] 0x60800002e3c0 __NSCFString**
**objc[54013]: [0x7fa3c9811048] ################ POOL 0x7fa3c9811048**
**objc[54013]: [0x7fa3c9811050] 0x7fa3ca800850 UIScreen**
**objc[54013]: [0x7fa3c9811058] 0x7fa3ca800850 UIScreen**
**objc[54013]: [0x7fa3c9811060] 0x6000002712c0 __NSCFDictionary**
**objc[54013]: [0x7fa3c9811068] 0x7fa3c8600bf0 UIWindow**
**objc[54013]: [0x7fa3c9811070] 0x60000008e6f0 __NSMallocBlock__**
**objc[54013]: [0x7fa3c9811078] 0x6000000567d0 __NSSetM**
**objc[54013]: [0x7fa3c9811080] 0x600000052de0 __NSSetM**
**objc[54013]: [0x7fa3c9811088] 0x600000053b30 __NSSetM**
**objc[54013]: [0x7fa3c9811090] 0x600000271640 __NSCFString**
**objc[54013]: [0x7fa3c9811098] 0x600000271640 __NSCFString**
**objc[54013]: [0x7fa3c98110a0] 0x608000017200 MyObject**
**objc[54013]: ##############**複製程式碼
在autoreleasepool裡有MyObject物件。
為什麼ARC沒有把物件放到autoreleasepool裡了?它是怎樣持有非自己生成的物件的?
程式碼:
{
id __strong obj = [NSMutableArray array];
}複製程式碼
轉換為編譯器模擬程式碼:
{
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
}複製程式碼
objc_retainAutoreleasedReturnValue函式,顧名思義:讓obj持有(retain)池中(autoreleased)返回的值(retuenValue);
程式碼:
+ (id)array {
return [[NSMutableArray alloc] init];
}複製程式碼
轉換為編譯器模擬程式碼:
+ (id)array {
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj);
}複製程式碼
objc_autoreleaseReturnValue函式,顧名思義:把obj註冊在池中(呼叫obj的autorelease方法),並返回。
因此按照上面的理解objc_autoreleaseReturnValue
將返回物件註冊到池子中,objc_retainAutoreleasedReturnValue`持有池中的物件。
但是事實不是那麼簡單:
但是objc_autoreleaseReturnValue遠遠不是autorelease那麼簡單。objc_autoreleaseReturnValue函式會檢查使用該函式的方法或函式呼叫方的執行命令列表,如果方法或函式的呼叫方在呼叫了方法或函式後緊接著呼叫了objc_retainAutoreleasedReturnValue,那就不會將返回的物件註冊到autoreleasepool中,而是直接傳遞到方法或函式的呼叫方。
因此objc_retainAutoreleasedReturnValue也不是retain那麼簡單,即使放回物件不註冊到autoreleasepool中,也能正確的獲取物件。
通過objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue協作,可以不將物件註冊到autoreleasepool中而直接傳遞,這一過程達到了最優化。
__weak修飾符的實現
現在我們新增一個weakTest變數,使用weak所有權修飾符
在ARC中實現:
@interface ARC() {
id test;
id __weak weakTest;
}
@end
@implementation ARC
- (void)create {
test = [self object];
weakTest = test;
_objc_autoreleasePoolPrint();
NSLog(@"ARC------------------ARC");
NSLog(@"after create count = %ld", _objc_rootRetainCount(test));
}
- (id)object {
id obj = [[MyObject alloc] init];
return obj;
}
- (void)printRetainCount {
NSLog(@"ARC------------------ARC");
NSLog(@"retain count = %ld", _objc_rootRetainCount(test));
}
@end複製程式碼
列印結果:
**2016-12-13 16:24:30.934 ARCLearn[55038:16489628] ARC------------------ARC**
**2016-12-13 16:24:30.935 ARCLearn[55038:16489628] after create count = 1**複製程式碼
由此可以看出__weak並沒有增加引用個數。
新增empty函式,使test為nil:
- (void)empty {
if (weakTest) {
NSLog(@"weak is %p", weakTest);
}
test = nil;
if (!weakTest) {
NSLog(@"weak change to nil");
}
}複製程式碼
列印結果:
**2016-12-13 16:33:06.301 ARCLearn[55349:16509066] weak is 0x608000011530**
**2016-12-13 16:33:06.301 ARCLearn[55349:16509066] myobject dealloc**
**2016-12-13 16:33:06.302 ARCLearn[55349:16509066] weak change to nil**複製程式碼
由此可以看出若附有__weak修飾符的變數所引用的物件被廢棄,則將nil賦值給該變數。
是如何實現將nil值賦值給引用為0的__weak物件?
{
id __weak obj1 = obj;
}複製程式碼
轉換為編譯器模擬程式碼:
{
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(obj1);
}複製程式碼
其中objc_initWeak(&obj1,obj)的實現:
obj1 = 0;
objc_storyWeak(&obj1, obj);複製程式碼
objc_destroyWeak的實現:
objc_storyWeak(&obj1, 0);複製程式碼
所以合在一起是:
{
id obj1;
obj1 = 0;
objc_storyWeak(&obj1, obj);
objc_storyWeak(&obj1, 0);
}複製程式碼
其中objc_storyWeak就是針對weak表(雜湊表)操作:
- 註冊到表中:objc_storeWeak函式把第二個引數的地址(雜湊值)作為鍵,將第一個引數的地址加入連結串列中(鍵值為一個連結串列,由於一個物件可同時賦值給多個附有__weak修飾符的變數),完成weak表的註冊。
- 從表中移除:如果第二個引數為0,則把第二個引數的地址的鍵從weak表中刪除,並且將鍵值置為nil(將連結串列中的值都置為nil)。
- 從表中查詢:使用weak表,將廢棄物件的地址作為鍵進行檢索,就能告訴的獲取對應的附有__weak修飾符的變數的地址。
廢棄引用為0的物件的流程:
- objc_release
- 因為引用計數為0所以執行dealloc
- 從weak表中獲取廢棄物件的地址為鍵的記錄
- 將包含在記錄中所有附有__weak修飾符變數的地址,賦值為nil
- 從weak表中刪除該記錄
- 從引用計數表中刪除廢棄物件的地址為鍵的記錄
__autoreleasing修飾符的實現
將物件賦值給附有__autoreleasing修飾符的變數等同於ARC無效時呼叫物件的autorelease方法。
ARC有效:
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
id __strong obj1 = [[NSObject alloc] init];
id __autoreleasing obj2 = obj1;
id __strong obj3 = [NSMutableArray array];
id __autoreleasing obj4 = obj3;
}複製程式碼
ARC無效:
@autoreleasepool {
id obj = [[NSObject alloc] init];
[obj autorelease];
id obj1 = [[NSObject alloc] init];
id obj2 = obj1;
[obj2 retain];
[obj2 autorelease];
id obj3 = [NSMutableArray array];
[obj3 retain];
id obj4 = obj3;
[obj4 retain];
[obj4 autorelease];
[obj3 release];
}複製程式碼
注意:顯式的附加autoreleasing修飾符同顯式地新增strong修飾符一樣罕見。我們經常非顯式的使用__autoreleasing修飾符。
隱式使用__autoreleasing:
- 編譯器檢查方法名是否以alloc/new/copy/mutableCopy開始,如果不是則自動將返回值的物件註冊到autoreleasepool(objc_retainAutoreleasedReturnValue和objc_autoreleaseReturnValue成對出現時就不會註冊到autoreleasepool)。
- id的指標或物件的指標在沒有顯式指定時會被附加上__autoreleasing修飾符。
如:
NSError *error = nil;
BOOL result = [obj performOperationWithError: &error];複製程式碼
performOperationWithError宣告為:
- (BOOL)performOperationWithError:(NSError **)error;複製程式碼
自動新增__autoreleasing的程式碼:
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error {
/* 錯誤發生 */
*error = [[NSError alloc] initwithDomain:MyAppDomain code:errorCode userInfo:nil];
return NO;
}複製程式碼
非ARC的實現:
- (BOOL)performOperationWithError:(NSError **)error {
/* 錯誤發生 */
*error = [[NSError alloc] initwithDomain:MyAppDomain code:errorCode userInfo:nil];
[*error autorelease];
return NO;
}複製程式碼
因為宣告為NSError __autoreleasing 型別的error作為*error被賦值,所以能夠返回註冊到autoreleasepool中的物件。
注意:賦值給物件指標時,所有權修飾符必須一致。
編譯器會自動加上所有權修飾符:NSError * __strong error = nil; NSError * __autoreleasing *pError = &error;複製程式碼
所有權修飾符不一致會產生編譯錯誤:
Initializing `NSError *__autoreleasing *` with an expression of type `NSError *__strong *` changes retain/release properties of pointer
__unsafe_unretained 修飾符
unsafe_unretained修飾符正如其名unsafe所示,是不安全的所有權修飾符。儘管ARC式的記憶體管理是編譯器的工作,但附有unsafe_unretained修飾符的變數不屬於編譯器的記憶體管理物件。
ARC實現:
id obj = [[NSObject alloc] init];
id __unsafe_unretained obj1 = obj;複製程式碼
非ARC實現:
id obj = [[NSObject alloc] init];
id obj1 = obj;複製程式碼
所以就是…編譯器啥都不做。這樣當物件的引用計數為0,被廢棄後,obj1就是不安全的了(懸垂指標),因為不會被置為nil。
注意:使用時確保物件確實存在。
什麼時候使用__unsafe_unretained?
- 在iOS4的應用程式中,必須使用unsafe_unretained修飾的變數,來替代weak修飾符。
- 不支援__weak修飾符的類,例如NSMachPork類,因為這些類重寫了retain/release並實現該類獨自的引用計數機制。
- allowsWeakReference/retainWeakReference例項方法(沒有寫入NSObject介面說明文件中)返回NO的情況。
Core Foundation物件的ARC
Core Foundation物件主要使用在用C語言編寫的Core Foundation框架中,並使用引用計數的物件。
- 在ARC無效時,Core Foundation框架中的retain/release分別是CFRetain/CFRelease。
- Foundation框架的API生成並持有的物件可以用Core Foundation框架的API釋放。當然,反過來也是可以的。
- 因為Core Foundation物件與Objective-C物件沒有區別,所以ARC無效時,只用簡單的C語言的轉換也能實現互換。因為這種轉換不需要使用額外的CPU資源,因此也被稱為“免費橋”(Toll-free Bridge)。
ARC無效時:
id obj = [[NSObject alloc] init];
void *p = obj; //void *相當與oc的id
id o = p;
[o release];複製程式碼
ARC有效時,會引起編譯錯誤,要使用“__bridge”轉換。
ARC有效時的程式碼:
id obj = [[NSObject alloc] init];
void *p = (__bridge void*)obj;
id o = (__bridge id)p;
[o release];複製程式碼
注意:bridge的安全性與賦值給unsafe_unretained修飾符相近,甚至會更低,如果管理時不注意賦值物件的所有者,就會因懸垂指標而導致程式崩潰。
__bridge轉換中還有另兩中轉換,分別為:
- bridge_retained轉換,相當於賦值給strong變數。
ARC有效:id obj = [[NSObject alloc] init]; void *p = (__bridge_retained void*)obj;複製程式碼
ARC無效:
id obj = [[NSObject alloc] init]; void *p = obj; [(id)p retain];複製程式碼
- bridge_transfer轉換,相當於賦值給strong變數後,該變數隨之釋放。
ARC有效void *p = 0; id obj = (__bridge_transfer id)p;複製程式碼
ARC無效
void *p = 0; id obj = (id)p; [obj retain]; [(id)p release];複製程式碼
簡書個人主頁:www.jianshu.com/users/b92ab…