使用ARC时NSURL和AVAudioPlayer的泄漏问题

20

我正在 iPhone 4S 上运行 Instruments。 我在这个方法中使用 AVAudioPlayer:

-(void)playSound{
    NSURL *url = [self.word soundURL];
    NSError *error;
    audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
    if (!error) {
        [audioPlayer prepareToPlay];
        [audioPlayer play];
    }else{
       NSLog(@"Problem With audioPlayer on general card. error : %@ | url %@",[error description],[url absoluteString]);
}

播放声音文件时出现泄漏:

泄漏对象:

1.

对象:NSURL

负责库:Foundation

负责帧:Foundation -[NSURL(NSURL) allocWithZone:]

2.

对象:_NSCFString

负责库:Foundation

负责帧:Foundation -[NSURL(NSURL) initFileURLWithPath:]

Instruments 没有直接指向我的代码,所以我很难找到泄漏原因。

我的问题是:

什么可能导致泄漏?或者当我不负责代码时,如何定位泄漏?

编辑 这是来自 Instruments 循环视图的模式: enter image description here 感谢 Shani


你是否启用了“僵尸对象”调试?这可能导致对象仍然留在内存中... - nielsbot
你的代码有一个错误:你没有将error初始化为nil,但是后面却测试了!error。(接受NSError **返回值的API不能保证在调用失败时设置error。在使用error之前,必须检查audioPlayer != nil。你还应该将error初始化为nil - nielsbot
1
我看了一下,苹果的代码可能存在泄漏问题。AVAudioPlayer实例1保留了传入的数据/URL,并创建了一个AVAudioPlayerCpp实例,该实例也保留了传入的数据/URL。当AVAudioPlayer被释放时,它会释放数据/URL,但我从相关的AVAudioPlayerCpp中没有看到释放。 - nielsbot
我已经发布了“证明”。也许有人想要再次检查一下... - nielsbot
5个回答

19
看起来是苹果公司的代码泄漏了... 我尝试使用了以下两种方法:
  • -[AVAudioPlayer initWithData:error:]
  • -[AVAudioPlayer initWithContentsOfURL:error:]
在第一种情况下,分配的 AVAudioPlayer 实例会保留传入的 NSData。而在第二种情况下,则会保留传入的 NSURL
我附上了一些仪器窗口的屏幕截图,显示了传入的 NSData 对象的保留/释放历史记录。

enter image description here

你可以看到,AVAudioPlayer 对象随后创建了一个 C++ 对象 AVAudioPlayerCpp,它再次保留了 NSData:

enter image description here

稍后,当 AVAudioPlayer 对象被释放时,NSData 将被释放,但是关联的 AVAudioPlayerCpp 从未调用释放...(您可以从附加的图像中看出)
如果想避免泄漏NSData/NSURL,似乎需要使用不同的解决方案来播放媒体..。
这是我的测试代码:
-(void)timerFired:(NSTimer*)timer
{
    NSString * path = [[ NSBundle mainBundle ] pathForResource:@"song" ofType:@"mp3" ] ;

    NSError * error = nil ;
    NSData * data = [ NSData dataWithContentsOfFile:path options:NSDataReadingMapped error:&error ] ;
    if ( !data )
    {
        if ( error ) { @throw error ; }
    }

    AVAudioPlayer * audioPlayer = data ? [[AVAudioPlayer alloc] initWithData:data error:&error ] : nil ;
    if ( !audioPlayer )
    {
        if ( error ) { @throw error ; }
    }

    if ( audioPlayer )
    {
        [audioPlayer play];
        [ NSThread sleepForTimeInterval:0.75 ] ;
        [ audioPlayer stop ] ;
    }
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // ...
    [ NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector( timerFired: ) userInfo:nil repeats:YES ] ;
    // ...
    return YES;
}

1
我有一个类似的泄漏问题...在iOS 5设备上运行分析器并没有显示任何泄漏,所以这很像是苹果iOS 6代码中的泄漏问题... - iMathieuB

1
根据之前的答案、我的研究和在苹果开发者论坛中的讨论,问题存在于苹果自己的iOS 6.0、6.0.1版本库中,据我所知,6.0.2版本也是如此。iOS 6.1已经解决了这个问题,我再也看不到泄漏了。
遗憾的是,这意味着如果你认为你的应用程序将在仍在运行iOS 6.0、6.0.1或6.0.2版本的手机上运行,你将不得不采取解决这些情况下泄漏的工作方法。
需要了解的是,无论用于初始化AVAudioPlayer的内容是什么,如果其他所有内容都按预期清除,则其retainCount似乎为1。
我的解决方法是将音频播放封装在它自己的类中,编译时使用-fno-objc-arc预处理器标志进行手动内存处理。
当释放所有内容时,我确保获取我用于初始化的NSURL(如果使用NSData初始化,结果相同)的retain count,然后再实际开始释放。
如果其他所有事情都处理正确,现在应该是1或2,所以只需释放正确的次数即可。
另外,在开始释放之前,请确保获取保留计数,否则在已修复该错误的iOS版本上将尝试访问已释放的对象。

0
这是一个与苹果库本身有关的问题。许多帖子(也包括在stackoverflow上)都报告了类似的问题。在这里你无能为力。

0

我理解在 ARC 项目中,作为开发人员,您不再需要负责保留/释放内存,因此该问题与苹果库有关,而不是您的代码。


0

如果这确实是苹果库中的一个错误,那么这里有一个不太好玩的解决方法。

对于相关的类,使其不启用 ARC。

可以通过进入目标的构建阶段来完成此操作。
转到编译源下拉菜单并找到您的类的 .m 文件。 输入 -fno-objc-arc 编译器标志列

不幸的是,您需要调整类并手动管理内存。

有关配置类以禁用 arc 的更详细说明,请参见 SO 上另一个问题的 this 答案

编辑

我想你可以将 AVAudioPlayer 行为封装到另一个类中,并在所有播放中使用该类。在这种情况下,您可以为一个文件设置 -fno-objc-arc,而无需处理整个应用程序的内存管理


我在我的应用程序中几乎在任何类中都有一个AVAudioPlayer,但无论如何感谢您的想法。 - shannoga
@shannoga 很不幸。我仍然喜欢管理我的内存,如果我使用使用ARC的开源软件,我需要使用那个标志。我必须为应用程序启用ARC,并在我创建的所有类上设置该标志。希望苹果很快解决这个问题。 - Jesse Black
看起来这是AVAudioPlayer内部的问题... 这不是ARC的问题。 - nielsbot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接