就像前面咱們看到的同樣,__weak 修飾符提供的功能如同魔法通常。shell
1,若附有__weak 修飾符的變量所引用的對象被廢棄,則將nil 賦值給該變量。express
2,使用附有__weak 修飾符的變量,便是使用註冊到autoreleasepool 中的對象。編程
這些功能像魔法同樣,到底發生了什麼,咱們一無所知。因此下面咱們來看看它們的實現。
框架
{ id __weak obj1 = obj; }
假設變量obj 附加__strong 修飾符且對象被賦值。
函數
/* 編譯器的模擬代碼 */ id obj1; objc_initWeak(&obj1, obj); objc_destroyWeak(&obj1);
經過objc_initWeak 函數初始化附有_ _ weak 修飾符的變量,在變量做用域結束時經過objc_destroyWeak 函數釋放該變量。
spa
如如下源代碼所示,objc_initWeak 函數將附有_ _ weak 修飾符的變量初始化爲0 後,會將賦值的對象做爲參數調用objc_storeWeak 函數。
指針
obj1 = 0; objc_storeWeak(&obj1, obj); //objc_destroyWeak 函數將0 做爲參數調用objc_storeWeak 函數。 objc_storeWeak(&obj1, 0);
即前面的源代碼與下列源代碼相同。
code
/* 編譯器的模擬代碼 */ id obj1;obj1 = 0; objc_storeWeak(&obj1, obj); objc_storeWeak(&obj1, 0);
objc_storeWeak 函數把第二參數的賦值對象的地址做爲鍵值,將第一參數的附有_ _ weak 修飾符的變量的地址註冊到weak 表中。orm
若是第二參數爲0,則把變量的地址從weak 表中刪除。對象
weak 表與引用計數表(參考1.2.4 節)相同,做爲散列表被實現。若是使用weak 表,將廢棄對象的地址做爲鍵值進行檢索,就能高速地獲取對應的附有_ _ weak 修飾符的變量的地址。
另外,因爲一個對象可同時賦值給多個附有_ _ weak 修飾符的變量中,因此對於一個鍵值,可註冊多個(weak)變量的地址。
釋放對象時,廢棄誰都不持有的對象的同時,程序的動做是怎樣的呢?下面咱們來跟蹤觀察。對象將經過objc_release 函數釋放。
(1)objc_release
(2)由於引用計數爲0 因此執行dealloc
(3)_objc_rootDealloc
(4)object_dispose
(5)objc_destructInstance
(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]; }
由於該源代碼將本身生成並持有的對象賦值給附有_ _ weak 修飾符的變量中,因此本身不能持有該對象,這時會被釋放並被廢棄,所以會引發編譯器警告。
warning: assigning retained obj to weak variable; obj will be released after assignment [-Warc-unsafe-retained-assign] id __weak obj = [[NSObject alloc] init]; ^ ~~~~~~~~~~~~~~
編譯器如何處理該源代碼呢?
/* 編譯器的模擬代碼 */ id obj; id tmp = objc_msgSend(NSObject, @selector(alloc)); objc_msgSend(tmp, @selector(init)); objc_initWeak(&obj, tmp); objc_release(tmp); // 由於此時只有一個強引用,因此對象被釋放了 objc_destroyWeak(&object);
雖然本身生成並持有的對象經過objc_initWeak 函數被賦值給附有_ _ weak 修飾符的變量中,但編譯器判斷其沒有持有者(即 強引用),故該對象當即經過objc_release 函數被釋放和廢棄。
這樣一來,nil 就會被賦值給引用廢棄對象的附有_ _ weak 修飾符的變量中。
下面咱們經過NSLog 函數來驗證一下。
{ id __weak obj = [[NSObject alloc] init]; NSLog(@"obj=%@", obj); }
如下爲該源代碼的輸出結果,其中用%@ 輸出nil。
obj=(null)
關於「當即釋放對象」
如前所述,如下源代碼會引發編譯器警告。
id __weak obj = [[NSObject alloc] init];
這是因爲編譯器判斷生成並持有的對象不能繼續持有。附有__unsafe_unretained修飾符的變量又如何呢?
id __unsafe_unretained obj = [[NSObject alloc] init];
與__weak修飾符徹底相同,編譯器判斷生成並持有的對象不能繼續持有,從而發出警告。
warning: assigning retained object to unsafe_unretained variable;
obj will be released after assignment [-Warc-unsafe-retained-assign]
id __unsafe_unretained obj = [[NSObject alloc] init];
^ ~~~~~~~~~~~~~~~~~~~~~~~
該源代碼經過編譯器轉換爲如下形式。
/* 編譯器的模擬代碼 */ id obj = objc_msgSend(NSObject, @selector(alloc)); objc_msgSend(obj, @selector(init)); objc_release(obj);
objc_release函數當即釋放了生成並持有的對象,這樣該對象的懸垂指針被賦值給變量obj中。
那麼若是最初不賦值變量又會如何呢?下面的源代碼在ARC無效時一定會發生內存泄漏。
[[NSObject alloc] init];
因爲源代碼不使用返回值的對象,因此編譯器發出警告。
warning: expression result unused [-Wunused-value]
[[NSObject alloc] init];
^~~~~~~~~~~~~~~~~~~~~~~
可像下面這樣經過向void型轉換來避免發生警告。
(void)[[NSObject alloc] init];
不論是否轉換爲void,該源代碼都會轉換爲如下形式
/* 編譯器的模擬代碼 */
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_release(tmp);
雖然沒有指定賦值變量,但與賦值給附有__unsafe_unretained修飾符變量的源代碼徹底相同。因爲不能繼續持有生成並持有的對象,因此編譯器生成了當即調用objc_release函數的源代碼。而因爲ARC的處理,這樣的源代碼也不會形成內存泄漏。
另外,能調用被當即釋放的對象的實例方法嗎?
(void)[[[NSObject alloc] init] hash];
該源代碼可變爲以下形式:
/* 編譯器的模擬代碼 */
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_msgSend(tmp, @selector(hash));
objc_release(tmp);
在調用了生成並持有對象的實例方法後,該對象被釋放。看來「由編譯器進行內存管理」這句話應該是正確的。
{ id __weak obj1 = obj; NSLog(@"%@", obj1); }
該源代碼可轉換爲以下形式:
/* 編譯器的模擬代碼 */ id obj1; objc_initWeak(&obj1, obj); id tmp = objc_loadWeakRetained(&obj1); objc_autorelease(tmp); NSLog(@"%@", tmp); objc_destroyWeak(&obj1);
與被賦值時相比,在使用附有_ _ weak 修飾符變量的情形下,增長了對objc_loadWeakRetained函數和objc_autorelease 函數的調用。這些函數的動做以下。
(1)objc_loadWeakRetained 函數取出附有_ _ weak 修飾符變量所引用的對象並retain。
(2)objc_autorelease 函數將對象註冊到autoreleasepool 中。
由此可知,由於附有_ _ weak 修飾符變量所引用的對象像這樣被註冊到autoreleasepool 中,因此在@autoreleasepool 塊結束以前均可以放心使用。可是,若是大量地使用附有_ _ weak 修飾符的變量,註冊到autoreleasepool 的對象也會大量地增長,所以在使用附有_ _ weak 修飾符的變量時,最好先暫時賦值給附有_ _ strong 修飾符的變量後再使用。
好比,如下源代碼使用了5 次附有_ _ weak 修飾符的變量o。
{ id __weak o = obj; NSLog(@"1 %@", o); NSLog(@"2 %@", o); NSLog(@"3 %@", o); NSLog(@"4 %@", o); NSLog(@"5 %@", o); }
相應地,變量o 所賦值的對象也就註冊到autoreleasepool 中5 次。
objc[14481]: ############## objc[14481]: AUTORELEASE POOLS for thread 0xad0892c0 objc[14481]: 6 releases pending. objc[14481]: [0x6a85000] ................ PAGE (hot) (cold) objc[14481]: [0x6a85028] ################ POOL 0x6a85028 objc[14481]: [0x6a8502c] 0x6719e40 NSObject objc[14481]: [0x6a85030] 0x6719e40 NSObject objc[14481]: [0x6a85034] 0x6719e40 NSObject objc[14481]: [0x6a85038] 0x6719e40 NSObject objc[14481]: [0x6a8503c] 0x6719e40 NSObject objc[14481]: ##############
將附有_ _ w e a k 修飾符的變量o 賦值給附有_ _ s t r o n g 修飾符的變量後再使用能夠避免此類問題。
{ id __weak o = obj; id tmp = o; NSLog(@"1 %@", tmp); NSLog(@"2 %@", tmp); NSLog(@"3 %@", tmp); NSLog(@"4 %@", tmp); NSLog(@"5 %@", tmp); }
在「tmp = o;」時對象僅登陸到autoreleasepool 中1 次。
objc[14481]: ############## objc[14481]: AUTORELEASE POOLS for thread 0xad0892c0 objc[14481]: 2 releases pending. objc[14481]: [0x6a85000] ................ PAGE (hot) (cold) objc[14481]: [0x6a85028] ################ POOL 0x6a85028 objc[14481]: [0x6a8502c] 0x6719e40 NSObject objc[14481]: ##############
在iOS4 和OS X Snow Leopard 中是不能使用_ _ weak 修飾符的,而有時在其餘環境下也不能使用。實際上存在着不支持_ _ weak 修飾符的類。
例如NSMachPort 類就是不支持_ _ weak 修飾符的類。這些類重寫了retain/release 並實現該類獨自的引用計數機制。可是賦值以及使用附有_ _ weak 修飾符的變量都必須恰當地使用objc4運行時庫中的函數,所以獨自實現引用計數機制的類大多不支持_ _ weak 修飾符。
不支持_ _ weak 修飾符的類,其類聲明中附加了「_ _ attribute_ _ ((objc_arc_weak_reference_unavailable))」這一屬性,同時定義了NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE。
若是將不支持_ _ weak 聲明類的對象賦值給附有_ _ weak 修飾符的變量,那麼一旦編譯器檢驗出來就會報告編譯錯誤。並且在Cocoa 框架類中,不支持_ _ weak 修飾符的類極爲罕見,所以沒有必要太過擔憂。
專欄allowsWeakReference/retainWeakReference 方法實際上還有一種狀況也不能使用__weak修飾符。
就是當allowsWeakReference/retainWeakReference實例方法(沒有寫入NSObject接口說明文檔中)返回NO的狀況。這些方法的聲明以下:
- (BOOL)allowsWeakReference;
- (BOOL)retainWeakReference;
在賦值給__weak修飾符的變量時,若是賦值對象的allowsWeakReference方法返回NO,程序將異常終止。
cannot form weak reference to instance (0x753e180) of class MyObject即對於全部allowsWeakReference方法返回NO的類都絕對不能使用__weak修飾符。這樣的類一定在其參考說明中有所記述。
另外,在使用__weak修飾符的變量時,當被賦值對象的retainWeakReference方法返回NO的狀況下,該變量將使用「nil」。如如下的源代碼:
{ id __strong obj = [[NSObjectalloc] init]; id __weak o = obj; NSLog(@"1 %@", o); NSLog(@"2 %@", o); NSLog(@"3 %@", o); NSLog(@"4 %@", o); NSLog(@"5 %@", o); }
因爲最開始生成並持有的對象爲附有__strong修飾符變量obj所持有的強引用,因此在該變量做用域結束以前都始終存在。所以以下所示,在變量做用域結束以前,能夠持續使用附有__weak修飾符的變量o所引用的對象。
1 <NSObject: 0x753e180>
2 <NSObject: 0x753e180>
3 <NSObject: 0x753e180>
4 <NSObject: 0x753e180>
5 <NSObject: 0x753e180>
下面對retainWeakReference方法進行試驗。咱們作一個MyObject類,讓其繼承NSObject類並實現retainWeakReference方法。
@interfaceMyObject : NSObject { NSUInteger count; } @end @implementationMyObject - (id)init { self = [super init]; return self; } - (BOOL)retainWeakReference { if (++count > 3) return NO; return [super retainWeakReference]; } @end
該例中,當retainWeakReference方法被調用4次或4次以上時返回NO。在以前的源代碼中,將從NSObject類生成並持有對象的部分更改成MyObject類。
{
id __strong obj = [[MyObject alloc] init];
id __weak o = obj;
NSLog(@"1 %@", o);
NSLog(@"2 %@", o);
NSLog(@"3 %@", o);
NSLog(@"4 %@", o);
NSLog(@"5 %@", o);
}
如下爲執行結果。
1 <MyObject: 0x753e180>
2 <MyObject: 0x753e180>
3 <MyObject: 0x753e180>
4 (null)
5 (null)
從第4次起,使用附有__weak修飾符的變量o時,因爲所引用對象的retainWeakRef-erence方法返回NO,因此沒法獲取對象。像這樣的類也一定在其參考說明中有所記述。
另外,運行時庫爲了操做__weak修飾符在執行過程當中調用allowsWeakReference/retainWeakReference方法,所以從該方法中再次操做運行時庫時,其操做內容會永久等待。本來這些方法並無記入文檔,所以應用程序編程人員不可能實現該方法羣,但若是因某些緣由而不得不實現,那麼仍是在所有理解的基礎上實現比較好。