2014-01-08 40 views
1

我將一個完成塊傳遞給我的方法,這個完成塊將在網絡請求完成時在後臺調用。不幸的是,如果主叫對象在同時釋放,應用程序崩潰:殭屍在後臺線程中調用完成塊時

視圖控制器(因爲它是從導航堆棧彈出可能被釋放)代碼:

__unsafe_unretained ViewController *weakSelf = self; 

[[URLRequester instance] sendUrl:url successBlock:^(id JSON) { 
    [weakSelf webserviceCallReturned:JSON]; 
}]; 

URLRequester碼(變得更簡單,當然):

- (void)sendUrl:(NSString *)urlAfterHost successBlock:(void (^)(id))successBlock { 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
     sleep(2); 
     successBlock(nil); 
     return; 
    }); 
} 

如果在這2秒內,ViewController從導航堆棧中彈出,應用程序崩潰。我錯過了什麼?

+0

'if(weakSelf){...}'? – Putz1103

+0

不幸的是,弱自己設置 – swalkner

+1

您可以將__unsafe__unretained更改爲__weak。從我讀的__weak處理釋放的對象,而__unsafe__unretained不處理。 – Putz1103

回答

2

當您使用__unsafe_unretained時,即使解除分配對象後引用仍然存在。所以如果視圖控制器被彈出,那麼weakSelf現在指向一個解除分配的對象。

如果將其更改爲__weak,則視圖控制器取消分配時,它會將weakSelf設置爲nil,您會沒事的。您甚至不需要檢查weakSelf是否設置爲任何值,因爲在nil上調用方法不起作用。

+0

你的建議很好,它應該解決問題,但我不同意。請看我的迴應。 – danh

+0

我不同意你的回答,因爲它應該保持弱點,因爲在視圖控制器已經彈出的情況下,你不想開始執行可能會導致問題的方法,因爲它不再是視圖層次結構。 – Gavin

+0

你能提供一個你擔心的「問題」的例子嗎?我想不出一個當vc位於導航堆棧頂部時也不會成爲問題的問題。無論如何,請注意我的答案中的測試,它不依賴於讀者對弱質量特性的瞭解。 – danh

2

似乎有不少人認爲塊內的「自我」必須始終是一個弱(或未保留)的副本。通常不是這種情況**。

在這種情況下,誤解是通過留下殭屍造成墜毀。正確的做法是直接引用塊中的自我(而不是unsafe_unretained,而不是弱),就像你希望普通代碼看起來一樣。其效果是塊會保留'self'指向的實例 - 在這種情況下是視圖控制器 - 並且它不會被銷燬,直到塊被銷燬(大概由url請求者)。

它會損害視圖控制器處理Web請求的結果,即使它已被彈出?幾乎肯定不是,但如果你認爲它會的話,檢查塊中的這種情況。

if (![self.navigationController.viewControllers containsObject:self]) 
    // I must have been popped, ignore the web request result 

    // Re the discussion in comments, I think a good coder should have misgivings about 
    // this condition. If you think you need it, ask yourself "why did I design 
    // my object so that it does something wrong based on whether some other object 
    // (a navigation vc in this case) contains it?" 

    // In that sense, the deliberate use of weakSelf is even worse, IMO, because 
    // it lets the coder ignore _and_obscure_ an important question. 
else { 
    // do whatever i do when the web request completes 
} 

**塊中對弱指針或不指定指針的需求源於塊將保留它們引用的對象的事實。如果其中一個對象直接或間接地保留了該塊,那麼您將得到一個循環(A保留B保留A)和一個泄漏。這可能會發生在塊引用的任何對象上,而不僅僅是「自我」。

但在你的情況下(如在很多情況下)自引用的視圖控制器不保留該塊。

+0

很好的解釋,謝謝!但是在這種情況下,'weak'不會更好,因爲'webserviceCallReturned:'方法沒有被調用(如果VC被彈出,則不需要)。 – swalkner

+1

這是一個有趣的問題。正如另一個答案指出的那樣,弱聲明會產生理想的效果。但我認爲這應該被認爲是一種副作用。我認爲我更喜歡簡單的代碼(沒有額外的限定符)和代碼清晰度(如果你需要的話,在代碼塊中有一個明確的條件),而不是我可愛的(太可愛了)。 – danh

+0

我同意這裏的弱點更好。我知道這並不總是必要的,但如果它被彈出,否則應該被釋放,在這裏它不應該做任何事情。實際上,在已經彈出的時候嘗試做某些事情在某些情況下實際上會導致問題。 – Gavin

0

使用塊(尤其是延遲塊)的良好做法是在調用方法中創建塊的本地副本。你的情況應該在做 - (無效)sendUrl:successBlock:

successBlockCopy = [successBlock copy]; 

,然後調用

successBlockCopy(nil); 

應該保留您的viewController一段時間,直到完成。

此外,最好使用__weak而不是__unsafe_unretained以避免突然釋放對象的問題。

+1

實際上這是不必要的,因爲由dispatch_async運行的塊將捕獲successBlock,它將複製它。所以它不需要再被複制,它只是多餘的。 – Gavin