蘋果的官方說明中稱,ARC是“由編譯器進行記憶體管理”的,但是實際上只有編譯其是無法完全勝任的,再次基礎上還需要Objective-C執行時庫的協助。
也就是說,ARC由以下工具、庫來實現。
- clang(LLVM編輯器)
- objc4 Objective-C 執行時庫
__strong 修飾符
{
id __strong obj = [[NSObject alloc] init];
}
複製程式碼
~~本人是c/cpp小白,沒有編譯成功,沒看到彙編輸出TAT~~
以上程式碼編譯器的模擬程式碼:
/** 編譯器的模擬程式碼 */
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);
複製程式碼
如上所示,呼叫了2次objc_msgSend
方法,變數的作用域結束時通過objc_release
釋放物件。雖然ARC有效時不能使用release方法,但由此可知編譯器自動插入了release
。
使用alloc/new/copy/mutableCopy以外的方法時:
{
id __strong obj = [NSMutableArray array];
}
複製程式碼
編譯器的模擬程式碼如下:
/** 編譯器的模擬程式碼 */
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
複製程式碼
其中的objc_retainAutoreleasedReturnValue
函式主要用於優化程式執行,它用於自己持有(retain)物件的函式,但它持有的物件應為返回註冊在autoreleasepool中物件的方法,或是函式的返回值。在呼叫alloc/new/copy/mutableCopy以外的方法,由編譯器插入該函式。
與 objc_retainAutoreleaseReturnValue
函式相對的函式為objc_autoreleaseReturnValue
函式。它用於alloc/new/copy/mutableCopy方法以外的NSMutableArray類的array類方法等返回物件的實現上。
+ (id) array
{
return [[NSArray alloc] init];
}
複製程式碼
轉換後
+ (id) array
{
id obj = objc_msgSend(NSArray,@selector(alloc));
objc_msgSend(obj,@selector(init));
return objc_autoreleaseReturnValue(obj);
}
複製程式碼
返回註冊到autoreleasepool中物件的方法使用了objc_autoreleaseReturValue
函式返回註冊到autoreleasepool中的物件。但是objc_autoreleaseReturValue
函式同objc_autorelease
函式不同,一般不僅限於註冊物件到autoreleasepool中。
objc_autoreleaseReturValue
函式會檢查使用該函式的方法或函式呼叫方的執行命令列表。如果方法或函式的呼叫方在呼叫了方法或函式後緊接著呼叫objc_retainAutoreleasedReturnValue()
函式,那麼就不將返回的物件註冊到autoreleasepool中,而直接傳遞到方法或函式的呼叫方。objc_retainAutoreleasedReturnValue
函式與objc_retain
函式不同,它即便不註冊到autoreleasepool中而返回物件,也能夠正確地獲取物件。
通過objc_autoreleaseReturnValue
函式和objc_retainAutoreleasedReturnValue
函式的協作,可以不講物件註冊到autoreleasepool中而直接傳遞,這一過程達到了最優化。
__weak 修飾符
- 若附有__weak修飾符的變數所引用的物件被廢棄,則將nil賦值給該變數。
- 使用附有__weak修飾符的變數,即是使用註冊到了autoreleasepool中的物件。
{
id __weak obj1 = obj;
}
複製程式碼
/** 編譯器的模擬程式碼 */
id obj1;
objc_initWeak(&obj1,obj);
objc_destroyWeak(&obj1);
複製程式碼
通過objc_initWeak
函式初始化附有__weak
修飾符的變數,在變數作用域結束時通過objc_destroyWeak
函式釋放該變數。
如以下原始碼所示,objc_initWeak
函式將附有__weak
修飾符的變數初始化為0後,會將賦值的物件作為引數呼叫objc_storeWeak函式。
obj1 = 0;
objc_storeWeak(&obj1,obj);
複製程式碼
objc_destroyWeak
函式將0作為引數呼叫objc_storeWeak函式。
objc_storeWeak(&obj1,0);
複製程式碼
即前面的原始碼和以下程式碼相同:
id obj1;
obj1 = 0;
objc_storeWeak(&obj1,obj);
objc_storeWeak(&obj1,0);
複製程式碼
objc_weakStore函式把第二引數的複製物件的地址作為鍵值,將第一引數的附有__weak修飾符的變數的地址註冊到weak表中,如果第二引數為0,則把變數的地址從weak表中刪除。
weak表與引用計數表相同,作為雜湊表被實現。如果使用weak表,將廢棄物件的地址作為鍵值進行檢索,能高速地獲取對應的附有__weak修飾符的變數的地址。另外,由於一個物件可以同時賦值給多個附有__weak修飾符的變數中,所以對於一個鍵值,可註冊多個變數的地址。
釋放物件時,廢棄誰都不持有的物件,通過objc_release函式釋放。
- objc_release
- 因為計數為0,所以執行dealloc
_objc_rootDealloc
- object_dispose
- objc_destructInstanse
- objc_clear_deallocating
物件被廢棄時最後呼叫的objc_clear_deallocating函式動作如下:
- 從weak表中獲取廢棄物件的地址為鍵值的記錄。
- 將包含在記錄中的所有附有__weak修飾符變數的地址,賦值為nil
- 從weak表中刪除該記錄。
- 從引用計數表中刪除廢棄物件的地址作為鍵值的記錄。
根據以上步驟,前面說的如果附有__weak修飾符的變數所引用的物件被廢棄,則將nil賦值給該變數這一功能即被實現。由此可知,如果大量使用附有__weak修飾符的變數,則會消耗相應的CPU資源,對此只在需要避免迴圈引用的時候使用__weak修飾符。
使用__weak修飾符時,以下程式碼會引起編譯器警告
{
id __weak obj = [[NSObject alloc] init];
NSLog(@"obj = %@",obj);
}
複製程式碼
編譯結果如下:
Assigning retained object to weak variable; object will be released after assignment
複製程式碼
編譯器模擬程式碼如下:
id obj;
id temp = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(temp,@selector(init));
objc_initWeak(&obj,temp);
objc_release(temp);
objc_destroyWeak(&obj);
複製程式碼
執行結果如下:
2017-12-07 19:37:24.075939+0800 ImageOrientation[10963:3581164] obj = (null)
複製程式碼
使用附有__weak修飾符的變數,即是使用註冊到autoreleasepool中的物件。
{
id __weak obj1 = obj;
NSLog(@"%@",obj1);
}
複製程式碼
該程式碼可以轉換為如下形式:
/** 編譯器模擬程式碼*/
id obj1;
objc_initWeak(&obj1,obj);
id temp = objc_loadWeakRetained(&obj1);
objc_autorelease(temp);
NSLog(@"%@",obj1);
objc_destroyWeak(&obj1);
複製程式碼
與賦值時相比,在使用附有__weak
修飾符變數的情形下,增加了對objc_loadWeakRetained
函式和objc_autorelease
函式的呼叫。這些函式的動作如下:
objc_loadWeakRetained
函式取出附有__weak修飾符變數所引用的物件並retainobjc_autorelease
函式將物件註冊到autoreleasepool中。
__autoreleasing 修飾符
將物件賦值給附有__autoreleasing修飾符的變數等同於MRC時呼叫物件的autorelease方法。
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
複製程式碼
模擬程式碼如下:
/** 編譯器的模擬程式碼 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_autoreleas(obj);
objc_autoreleasePoolPop(pool);
複製程式碼
alloc/new/copy/mutableCopy之外的方法實現:
@autoreleasepool{
id __autoreleasing obj = [NSMutableArray array];
}
複製程式碼
/** 編譯器的模擬程式碼 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutorelesedReturnedValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
複製程式碼
引用計數
獲取引用計數的函式為CFGetRetainCount
例如:
{
id __strong obj = [[NSObject alloc] init];
NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}
結果為1
複製程式碼
{
id __strong obj = [[NSObject alloc] init];
id __weak obj1 = obj;
NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}
結果為2
複製程式碼
{
id __strong obj = [[NSObject alloc] init];
id __autoreleaing obj1 = obj;
NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}
結果為2
複製程式碼
{
id __strong obj = [[NSObject alloc] init];
@autoreleasepool{
id __autoreleaing obj1 = obj;
NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}
}
NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}
結果為2和1
複製程式碼