【问题标题】:Potential Leak of an Object that is released before reuse and dealloc'd on exit在重用之前释放并在退出时释放的对象的潜在泄漏
【发布时间】:2013-05-21 18:34:48
【问题描述】:

使用 xcode 分析器时,我收到关于对象潜在泄漏的警告。这个警告令人困惑,我需要一些解释为什么我会收到这个错误。下面是 mediaSources 保存指向相关对象的指针的代码:

在 .h 文件中,创建指向 MediaSources 类的指针并赋予保留属性:

@interface RootViewController : UIViewController <...>
{
    ...

    MediaSources                *mediaSources;

    ...
}

@property (nonatomic, retain)   MediaSources            *mediaSources;

.m文件中(rootViewController)是一个可以多次调用的方法。因此,我在每个条目上释放对象并分配一个新对象。 MediaSources 对象执行后台任务,所以在我知道它完成之前我不想释放它。如果我在分配类的行上使用 autoRelease,它会崩溃。 :

-(void) getSelectedMediaSources
{
    [self setMediaSources: nil];    // release old stuff and nilify 
    [self setMediaSources: [[MediaSources alloc] init]]; 
    [self.mediaSources checkForMediaSourceUpdates];
}

同样在.m文件中,mediaSources也被合成,在dealloc中释放

@synthesize mediaSources;

...

- (void)dealloc {
    ...
    [mediaSources release];
    ...

    [super dealloc];
}

请解释我收到此警告的原因。我不明白怎么可能有泄漏。 Dealloc 应该释放这个对象的最后一个副本。

响应 checkForMediaSourceUpdates 的代码请求。这会变得有点复杂,但下面是本质:

(void) checkForMediaSourceUpdates
{    
    NSString *s = [NSString  stringWithFormat:@"http://www.mywebsite.com/mediaSources/%@/mediaSources.plist", countryCode];

    NSURL *url = [NSURL URLWithString:s];
    NSURLRequest *req = [NSURLRequest requestWithURL:url  cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:60.0];
    MyDownloader *d = [[MyDownloader alloc] initWithRequest:req];
    [self.connections addObject:d];
    [[NSNotificationCenter defaultCenter] addObserver:self  selector:@selector(checkForMediaSourceUpdatesDownloadFinished:) name:@"connectionFinished" object:d];
    [d.connection start];
    [d release];
}

-(void) checkForMediaSourceUpdatesDownloadFinished: (NSNotification *) n 
{
    MyDownloader *d = [n object];
    NSData *data = nil;
    if ([n userInfo]) {
        NSLog(@"In checkForMediaSourceUpdatesDownloadFinished: MyDownloader returned an error");
    }
    else {
        data = [d receivedData];

        // do something with the data
    }
}

myDownloader 类执行输入 NSURLRequest 中指定的文件下载。完成下载后,该类会生成一个名为“connectionFinished”的NSNotification。此类的用户必须注册此通知并处理此类的所有清理操作。如果下载失败,该类将生成一个 NSNotification,也称为“connectionFinished”,但添加了指示发生错误的 userInfo。同样,此类的用户必须注册此通知并处理此类的所有清理操作。

【问题讨论】:

  • 请显示checkForMediaSourceUpdates的代码

标签: ios objective-c memory-management memory-leaks


【解决方案1】:

根据定义,您将自动释放的对象传递给合成的 setter。 setter 本身保留了对象,因此下面这行是错误的:

[self setMediaSources: [[MediaSources alloc] init]]; 

应该是:

[self setMediaSources: [[[MediaSources alloc] init] autorelease]]; 

你也不需要在之前用nil 调用你的setter。当通过 setter 设置不同的对象时,旧对象被释放。合成的 setter 看起来像:

- (void) setMediaSources:(MediaSources *)mediaSources {
  if (_mediaSources != mediaSources) {
    [_mediaSources release];
    _mediaSources = [mediaSources retain];
  }
}

问题是:为什么每次调用getSelectedMediaSources 时都需要分配一个新的MediaSource?释放自动释放的MediaSource 时,您期望发生什么崩溃?您是将其设置为另一个对象的委托还是将其注册到NSNotificationCenter?如果是这样,请不要忘记取消代理或将其从通知中心删除。

【讨论】:

  • 我之前尝试过使用自动释放,但应用程序偶尔会在自动释放时崩溃。当时我断定它一定是崩溃了,因为该对象应该在后台接收的下载结果已经无法接收,因为该对象已被释放。它不会经常发生,但似乎是一个小窗口,当该对象被释放时会发生一些不好的事情。这有意义吗?
  • 当然,如果您将MediaSources 注册为另一个对象的委托,并且该对象尝试触发MediaSource 上的委托方法,该方法已经被释放并且您没有设置委托给nil,它崩溃了!
  • 好的,看来我不应该使用自动释放。如果我不使用 setter 会更合适吗(例如 mediaSources = [[MediaSources alloc] init]];)?
  • 也许您在checkForMediaSourceUpdates 中向我们提供了更多您在做什么的信息。没有这个,我们就无法给出适当的提示。
  • 好的,谢谢。我在原始问题中添加了请求的信息。
【解决方案2】:

正如其他答案中已经提到的,您应该像这样将 autoreleased 实例传递给 setter:

[self setMediaSources: [[[MediaSources alloc] init] autorelease]]; 

您因为这样做而看到的崩溃似乎源于您在checkForMediaSourceUpdates 中进行后台处理的事实。如果要在此方法中进行后台处理,则应确保self 在后台任务的整个持续时间内保持有效。在开头手动调用[self retain]; 并在结尾手动调用[self release]; 是完全有效的。 (注意:GCD 块会自动保留/释放 self 对象,如果您从块内引用它)。

编辑:我希望你现在知道问题所在,mAu 称它为。在执行请求时,MediaSources 实例上只有一个保留计数(通过 RootViewController 对象)。在请求完成之前,您再次调用该方法,导致MediaSources 对象被释放。因此,当请求完成并且NSNotification 尝试发送回调消息时,应用程序崩溃,因为观察者实例已经消失。

有两种方法可以解决这个问题(使用更适合您的情况的方法):

  1. checkForMediaSourceUpdates[self release]; 的末尾添加[self retain]; checkForMediaSourceUpdatesDownloadFinished:
  2. 或者,在-[MediaSources dealloc] 中添加[[NSNotificationCenter defaultCenter] removeObserver: self]

顺便说一句,您应该考虑why do you need to allocate a new MediaSource on every call to getSelectedMediaSources? 以及为什么每次您可能有一个正在运行的请求时都启动一个新请求。你的设计有很大的问题。

【讨论】:

  • 好的,谢谢。我在原始问题中添加了请求的信息。
  • 我同意。设计随着时间的推移而变形,我从未正确清理过它。事实上,在我的设计中,如果有一个正在运行的请求,就不可能启动一个新请求,但无论如何,它都需要清理。尽管如此,感谢您的回答。这很有帮助。
【解决方案3】:
[self setMediaSources: [[MediaSources alloc] init]]; 

您要保留新的 MediaSources 对象两次,一次是使用 alloc,另一次是使用 setMediaSources。你只释放一次。

试试:

[self setMediaSources: [[[MediaSources alloc] init] autorelease]]; 

从 cmets 跟进:

是的,您可以直接设置变量,但这并不能解决您的根本问题。听起来您现有的代码因为额外的保留而有效,并且在您平衡保留/释放时失败。您需要找出谁在保留对旧 mediaSources 对象的弱引用,并在释放它之前中断该引用。

【讨论】:

  • 那么,这是否意味着如果我不使用 setter(例如 mediaSources = [[MediaSources alloc] init]];)就不会发生泄漏?
  • @JeffB6688 是正确的,如果您直接设置 ivar,您将获得正确的保留计数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-04
  • 2014-08-08
  • 2021-05-21
  • 2010-11-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多