Objective-C 之ARC實現

蘋果的官方說明中稱,ARC是「由編譯器進行內存管理」的,可是實際上只有編譯其是沒法徹底勝任的,再次基礎上還須要Objective-C運行時庫的協助。 也就是說,ARC由如下工具、庫來實現。bash

  • 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];
}
複製代碼

編譯器的模擬代碼以下:ui

/** 編譯器的模擬代碼 */
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
複製代碼

其中的objc_retainAutoreleasedReturnValue函數主要用於優化程序運行,它用於本身持有(retain)對象的函數,但它持有的對象應爲返回註冊在autoreleasepool中對象的方法,或是函數的返回值。在調用alloc/new/copy/mutableCopy之外的方法,由編譯器插入該函數。spa

objc_retainAutoreleaseReturnValue函數相對的函數爲objc_autoreleaseReturnValue函數。它用於alloc/new/copy/mutableCopy方法之外的NSMutableArray類的array類方法等返回對象的實現上。code

+ (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
複製代碼
相關文章
相關標籤/搜索