2010-10-31 198 views
17

我想弄清楚以下情況的推薦做法。某些對象(如CLLocationManager或MKReverseGeocoder)將其結果異步發送給委託回調方法。在回調方法中釋放CLLocationManager或MKReverseGeocoder實例(或者其他類)可以嗎?重點是你不再需要這個對象,所以你告訴它停止發送更新,將它的委託設置爲零,並釋放對象。在其委託回調方法中釋放委託對象

僞代碼:

@interface SomeClass <CLLocationManagerDelegate> 
... 
@end 

@implementation SomeClass 

... 

- (void)someMethod 
{ 
    CLLocationManager* locManager = [[CLLocationManager alloc] init]; 
    locManager.delegate = self; 
    [locManager startUpdatingLocation]; 
} 

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation 
{ 
    // Do something with the location 
    // ... 

    [manager stopUpdatingLocation]; 
    manager.delegate = nil; 
    [manager release]; 
} 

@end 

我想知道如果這個使用模式被認爲是永遠OK,如果它被認爲是永遠OK,或者如果它取決於類?

有一個明顯的情況,即釋放委託對象會出錯,​​也就是說,如果它在通知委託後需要做些什麼。如果代理釋放該對象,則其內存可能會被覆蓋並且應用程序崩潰。 (這似乎是在我的應用程序與CLLocationManager在特定情況下發生了什麼,兩者都只在模擬器上,我試圖弄清楚它是模擬器錯誤,還是我所做的是從根本上有缺陷。)

我一直在尋找,我無法找到一個確鑿的答案。有沒有人有權回答這個問題的權威來源?

+0

如果你看到崩潰 - 你可以嘗試的另一件事是將它設置爲* autorelease *而不是明確地釋放它。雖然,不確定地知道,這可能只是混淆問題,而不是修復它的真實... – Brad 2010-10-31 19:33:19

+0

有趣的是,我沒有考慮到這一點。不過,我不知道哪個autorelease池會選擇它。 – Hollance 2010-10-31 21:07:17

+1

我不確定,但不知怎的,這樣做感覺不對。 – 2010-10-31 21:50:26

回答

5

這是一個很好的問題,我等了幾個小時,希望有人能給出足夠的答案,但因爲沒有人甚至回答,我會試一試。首先我會評論你的方法,然後我試着建議我會如何解決這個問題。

這絕對是一個非常糟糕的想法釋放 - 因此從其代理釋放一個對象。考慮一下對象(如CLLocationManager)如何調用它們的委託 - 他們只是在某種方法中調用它們。當委託調用完成後,代碼的執行會返回到已經被釋放的對象的方法。 BAM!

讓我們暫時忘記這是一個糟糕的主意。我看到兩個選項如何修復那很容易。首先,autorelease而不是release會給對象留下更長時間的垃圾郵件 - 它至少會在委託人返回時存活。對於大多數情況來說,這應該足夠了,至少如果API的作者完成了她的工作並且在主API類後面封裝了邏輯(在CLLocationManager的情況下它可能正在等待GPS關閉...)。第二種選擇是延遲發佈(想起performSelector:withObject:afterDelay:),但這更適用於執行不力的API。

所以如果釋放它不是一個好主意,那麼是什麼?

那麼,你真的通過釋放CLLocationManager獲得了什麼?釋放這些少量字節的內存不會在系統內存不足時保存您的應用程序終止。無論如何,真的只有一次,你需要當前用戶的位置?

我建議你將與CLLocationManager相關的任務封裝到一個單獨的類中,甚至可能是一個單獨的類 - 該類將成爲它的委託,並且它將負責與CLLocationManager進行通信並通知應用程序有關結果(可能通過發送NSNotification )。 CLLocationManager將從該類的dealloc中釋放,並且永遠不會因委託回調而被釋放。釋放幾個字節的內存就足夠了 - 當你的應用程序進入後臺時,你可以做到這一點,但只要你的應用程序運行,釋放這幾個字節不會使內存消耗有任何顯着的改善。

** **加入

是很自然的,和正確的,對於委託有它的作用不如授對象的所有權。但是代理不應該因爲回調而釋放對象。這有一個例外,它是回調,告訴你處理結束。作爲一個例子,NSURLConnectionconnectionDidFinishLoading:在文檔中聲明「代表將不會收到進一步的消息」。你可以讓一個班級下載一堆文件,每個文件都有不同的NSURLConnection(讓你的班級作爲委託),分配和釋放他們作爲文件下載的進度。

CLLocationManager的行爲是不同的。你的程序中應該只有一個CLLocationManager實例。該實例由一些代碼管理,可能是一個單例 - 當應用程序進入後臺時可以釋放,在喚醒時重新初始化。 CLLocationManager的生命週期將與其管理類相同,該管理類也充當代表。

+0

所有優點。我知道如何解決這個問題,我只是想知道什麼是合適的。我問這個問題的原因之一是某些半權威來源(如Erica Sadun的Cookbook)在某些示例中確實會從委託回調中釋放委託對象。那麼在某些情況下(以及如何知道這是哪種情況)還是這些例子完全錯誤? – Hollance 2010-11-01 06:13:12

+0

發佈始終是個壞主意。 Autoreleasing,這取決於 - 它可能工作,但沒有保證。 API應該以Manager類作爲庫和用戶代碼之間的接口的方式編寫,因此用戶可以自由發佈它,並且庫可以自行管理,直到完成清理(網絡連接,硬件,GPS修復等等)爲止, 。將所有這些懷疑放在一邊,並保留該對象,特別是如果它支持像CLLocationManager中的stopUpdatingLocation一樣暫停。 – Michal 2010-11-01 08:25:20

+0

,因爲評論長度有限,我在答案的底部添加了更多內容。 – Michal 2010-11-01 10:38:09

5

不是。該模式總是被認爲是錯誤的。它打破了Cocoa Memory Management Rules。管理器對象作爲參數傳入。你沒有通過新的,分配或複製來獲得它,也沒有保留它。你因此一定不能釋放它。

+0

但是I * did *分配了實例,這就是我在委託方法中得到的指針。我可以讓'locManager'成爲一個實例變量,並釋放出來,而不是本地'manager'變量,但這仍然是我釋放的同一個實例。所以它不會改變我的問題的性質。 – Hollance 2010-11-01 06:07:09

+2

@Hollance:不是你在那個範圍內。規則很清楚。你*不能*釋放對象。它作爲參數傳遞給你,因此呼叫者有權指望你**不**做任何事情使其消失。 – JeremyP 2010-11-01 09:18:11

+0

誰投了票,請解釋原因。我的回答是事實正確的。 – JeremyP 2010-11-01 09:32:45

3

就像米歇爾說,絕對沒有理由釋放經理對象爲memeory儲蓄。另外,就像JeremyP所說的那樣,在另一個只接收該對象的函數中釋放一個對象將是完全不正確的模式明智和設計明智的。它違背所有規則。

但是,正確的做法是簡單地停止更新並將管理員委派設置爲零,因爲您已經在做。所以你唯一需要刪除的是[manager release]行。

我的猜測是你正在創建一個本地範圍內的經理,因此正在設法弄清楚如何確保經理獲得釋放。在這裏做的正確的事情是創建一個管理器作爲你的類的實例變量,然後在類dealloc中釋放它。

1

爲什麼你的模式是一個壞主意的另一個原因是,對於CLLocationManager之類的東西,如果屏幕進入休眠狀態,通常需要告訴它停止接收更新 - 只有在某個地方維護引用時才能這樣做你可以告訴開始/停止。如果你保持一個參考,那麼你可以完全管理它。

+0

是的,但並不完全與此問題相關。我可以把'locManager'作爲一個實例變量。但是當我完成之後,我仍然想要釋放CLLocationManager,之後我不再需要參考。 – Hollance 2010-11-01 06:16:28