天天看點

__Weak的用法

摘要 block中用到的外部變量最好使用 __weak 修飾,避免記憶體洩露;block容易引起引用循環的根本原因是: 1,對于(block内部用到的)外部變量,對其執行retain 的時機 與該block的執行時機是不同步的,在block聲明的時候就對外部變量進行了retain,而block何時執行甚至是否執行都是不可預測的;2,block 一般是匿名的,而且copy指派的,手動釋放block對象比較困難

目錄[-]

  • 1,若附有__weak 修飾符的變量所引用的對象被廢棄,則将nil 指派給該變量。
  • 2,這次我們再用附有_ _ weak 修飾符的變量來确認另一功能:使用附有_ _ weak 修飾符的變量,即是使用注冊到autoreleasepool 中的對象。

就像前面我們看到的一樣,__weak 修飾符提供的功能如同魔法一般。

1,若附有__weak 修飾符的變量所引用的對象被廢棄,則将nil 指派給該變量。

2,使用附有__weak 修飾符的變量,即是使用注冊到autoreleasepool 中的對象。

1,若附有__weak 修飾符的變量所引用的對象被廢棄,則将nil 指派給該變量。

這些功能像魔法一樣,到底發生了什麼,我們一無所知。是以下面我們來看看它們的實作。

?

1 2 3

{   

id __weak obj1 = obj;

}

假設變量obj 附加__strong 修飾符且對象被指派。

?

1 2 3 4

id obj1;

objc_initWeak(&obj1, obj);

objc_destroyWeak(&obj1);

通過objc_initWeak 函數初始化附有_ _ weak 修飾符的變量,在變量作用域結束時通過objc_destroyWeak 函數釋放該變量。

如以下源代碼所示,objc_initWeak 函數将附有_ _ weak 修飾符的變量初始化為0 後,會将指派的對象作為參數調用objc_storeWeak 函數。

?

1 2 3 4 5

obj1 = 0;

objc_storeWeak(&obj1, obj);

//objc_destroyWeak 函數将0 作為參數調用objc_storeWeak 函數。

objc_storeWeak(&obj1, 0);

即前面的源代碼與下列源代碼相同。

?

1 2 3 4

id obj1;obj1 = 0;

objc_storeWeak(&obj1, obj);

objc_storeWeak(&obj1, 0);

objc_storeWeak 函數把第二參數的指派對象的位址作為鍵值,将第一參數的附有_ _ weak 修飾符的變量的位址注冊到weak 表中。

如果第二參數為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. (1)從weak 表中擷取廢棄對象的位址為鍵值的記錄。
  2. (2)将包含在記錄中的所有附有_ _ weak 修飾符變量的位址,指派為nil。
  3. (3)從weak 表中删除該記錄。
  4. (4)從引用計數表中删除廢棄對象的位址為鍵值的記錄。

根據以上步驟,前面說的如果附有_ _ weak 修飾符的變量所引用的對象被廢棄,則将nil 指派給該變量這一功能即被實作。由此可知,如果大量使用附有_ _ weak 修飾符的變量,則會消耗相應的CPU 資源。良策是隻在需要避免循環引用時使用_ _ weak 修飾符。

使用_ _ weak 修飾符時,以下源代碼會引起編譯器警告。

?

1 2 3

{

id __weak obj = [[NSObject alloc] init];

}

因為該源代碼将自己生成并持有的對象指派給附有_ _ weak 修飾符的變量中,是以自己不能持有該對象,這時會被釋放并被廢棄,是以會引起編譯器警告。

?

1 2 3 4

warning: assigning retained obj to weak variable; obj will be

released after assignment [-Warc-unsafe-retained-assign]

id

__weak obj = [[NSObject alloc] init];

^     ~~~~~~~~~~~~~~

編譯器如何處理該源代碼呢?

?

1 2 3 4 5 6 7

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 函數來驗證一下。

?

1 2 3 4

{

id __weak obj = [[NSObject alloc] init];

NSLog(@

"obj=%@"

, obj);

}

以下為該源代碼的輸出結果,其中用%@ 輸出nil。

?

1

obj=(null)

關于“立即釋放對象”

如前所述,以下源代碼會引起編譯器警告。

?

1

id __weak obj = [[NSObject alloc] init];

這是由于編譯器判斷生成并持有的對象不能繼續持有。附有__unsafe_unretained修飾符的變量又如何呢?

?

1

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];

                           ^     ~~~~~~~~~~~~~~~~~~~~~~~

該源代碼通過編譯器轉換為以下形式。

?

1 2 3 4

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);

在調用了生成并持有對象的執行個體方法後,該對象被釋放。看來“由編譯器進行記憶體管理”這句話應該是正确的。

2,這次我們再用附有_ _ weak 修飾符的變量來确認另一功能:使用附有_ _ weak 修飾符的變量,即是使用注冊到autoreleasepool 中的對象。

?

1 2 3 4

{

id __weak obj1 = obj;

NSLog(@

"%@"

, obj1);

}

該源代碼可轉換為如下形式:

?

1 2 3 4 5 6 7

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。

?

1 2 3 4 5 6 7 8

{

id __weak o = obj;

NSLog(@

"1 %@"

, o);

NSLog(@

"2 %@"

, o);

NSLog(@

"3 %@"

, o);

NSLog(@

"4 %@"

, o);

NSLog(@

"5 %@"

, o);

}

相應地,變量o 所指派的對象也就注冊到autoreleasepool 中5 次。

?

1 2 3 4 5 6 7 8 9 10 11

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 修飾符的變量後再使用可以避免此類問題。

?

1 2 3 4 5 6 7 8 9

{

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 次。

?

1 2 3 4 5 6 7

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”。如以下的源代碼:

?

1 2 3 4 5 6 7 8 9

{

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方法。

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

@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方法,是以從該方法中再次操作運作時庫時,其操作内容會永久等待。原本這些方法并沒有記入文檔,是以應用程式程式設計人員不可能實作該方法群,但如果因某些原因而不得不實作,那麼還是在全部了解的基礎上實作比較好。

繼續閱讀