Objective-C 之ARC實現

小白HAKU發表於2019-02-23

蘋果的官方說明中稱,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函式釋放。

  1. objc_release
  2. 因為計數為0,所以執行dealloc
  3. _objc_rootDealloc
  4. object_dispose
  5. objc_destructInstanse
  6. objc_clear_deallocating

物件被廢棄時最後呼叫的objc_clear_deallocating函式動作如下:

  1. 從weak表中獲取廢棄物件的地址為鍵值的記錄。
  2. 將包含在記錄中的所有附有__weak修飾符變數的地址,賦值為nil
  3. 從weak表中刪除該記錄。
  4. 從引用計數表中刪除廢棄物件的地址作為鍵值的記錄。

根據以上步驟,前面說的如果附有__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函式的呼叫。這些函式的動作如下:

  1. objc_loadWeakRetained函式取出附有__weak修飾符變數所引用的物件並retain
  2. objc_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
複製程式碼

相關文章