在委托回调方法中释放委托对象

时间:2010-10-31 19:00:21

标签: iphone objective-c cocoa-touch memory-management

我正在试图弄清楚以下情况的推荐做法。某些对象(如CLLocationManager或MKReverseGeocoder)将其结果异步发送到委托回调方法。可以在回调方法中释放CLLocationManager或MKReverseGeocoder实例(或者它可能是什么类)吗?关键是你不再需要那个对象,所以你告诉它停止发送更新,将其委托设置为nil,然后释放对象。

伪代码:

@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

我想知道这种使用模式是否被认为总是好的,如果认为它永远不行,或者它是否依赖于类?

有一个明显的例子,释放委托对象会出错,​​即如果它在通知委托后需要做的事情。如果委托释放对象,则其内存可能会被覆盖并且应用程序崩溃。 (这似乎是我的应用程序在特定环境中使用CLLocationManager时发生的情况,仅在模拟器上。我正在试图弄清楚它是否是模拟器错误或者我正在做的事情是否存在根本缺陷。)

我一直在寻找,但我无法找到确凿的答案。有没有人有一个可以回答这个问题的权威来源?

4 个答案:

答案 0 :(得分:5)

这是一个非常好的问题,我等了几个小时,希望有人会给出足够的答案,但因为没有人回复,我会试一试。首先,我会评论你的方法,然后我试着建议我如何解决这个问题。

发布这绝对是一个非常糟糕的主意 - 因此从其委托中释放一个对象。试想一下对象(如CLLocationManager)如何调用它们的委托 - 它们只是在某个方法的中间调用它们。当对委托的调用完成后,代码的执行将返回到已经解除分配的对象的方法。 BAM!

让我们暂时忘记这是一个坏主意。我看到两个选项如何轻松地修复。首先,autorelease而不是release为对象提供了更长时间的垃圾邮件 - 它至少可以从委托中返回。对于大多数情况来说这应该足够了,至少如果API的作者完成了她的工作并且在主API类后面封装了逻辑(在CLLocationManager的情况下它可能正在等待GPS关闭......)。第二种选择是延迟发布(performSelector:withObject:afterDelay:浮现在脑海中),但这更像是一种解决方法,用于严格实施的API。

因此,如果发布它不是一个好主意,那么它是什么?

那么,你通过发布CLLocationManager真正获得了什么?释放这几个字节的内存不会导致您的应用程序在系统内存不足时终止。无论如何,你真的只需要一次当前用户的位置吗?

我建议你将与CLLocationManager相关的任务封装到一个单独的类中,甚至可能是一个单独的类 - 该类将成为它的委托,它将负责与CLLocationManager通信并通知你的应用程序结果(可能通过发送{{ 1}})。 CLLocationManager将从该类的NSNotification中释放,并且永远不会作为委托回调的结果。 dealloc应该足够了,释放几个字节的内存 - 好吧,你可以在应用程序进入后台时执行此操作,但只要你的应用程序运行,释放这几个字节就不会显着改善内存消耗。 / p>

**加法**

委托人拥有作为委托的对象的所有权是自然而且正确的。但是代理不应该因回调而释放对象。但是有一个例外,它是回调告诉你处理结束了。例如,stopUpdatingLocation的{​​{1}}在文档中说明“委托将不再收到任何消息”。您可以让一个类下载一堆文件,每个文件都有不同的NSURLConnection(将您的类作为委托),分配并释放它们作为文件下载进度。

connectionDidFinishLoading:的行为是不同的。您的程序中应该只有一个CLLocationManager实例。该实例由一些代码管理,可能是一个单例 - 一个可以在应用程序进入后台时释放,在唤醒时重新初始化。 NSURLConnection的生命周期与其管理类相同,后者也充当代表。

答案 1 :(得分:5)

没有。这种模式总是被认为是错误的。它打破了Cocoa Memory Management Rules。 manager对象作为参数传入。您没有通过新的,分配或复制获得它,也没有保留它。因此,您不得释放它。

答案 2 :(得分:3)

就像Michal所说的那样,完全没有理由释放经理对象来记忆。 此外,就像JeremyP所说的那样,在另一个只接收该对象的函数中释放一个对象是完全错误的模式和设计明智。这违反了所有规则。

但是,正确的做法是简单地停止更新并将管理器委托给nil,就像你已经在做的那样。因此,您唯一需要删除的是[经理发布]行。

我的猜测是你在本地范围内创建一个经理,因此正试图找出如何确保经理被释放。这里要做的正确的事情是将管理器创建为类的实例变量,然后在类dealloc中释放它。

答案 3 :(得分:1)

你的模式是一个坏主意的另一个原因是,对于像CLLocationManager这样的东西你通常想告诉它在屏幕进入睡眠状态时停止接收更新 - 你只能在你某处保留引用的情况下这样做可以告诉开始/停止。如果你要维护一个参考,那么你也可以完全管理它。