2010-10-31 129 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