Leak from NSURL and AVAudioPlayer using ARC

只谈情不闲聊 提交于 2019-11-27 12:47:22

Looks to be a leak in Apple's code... I tried using both

  • -[AVAudioPlayer initWithData:error:] and
  • -[AVAudioPlayer initWithContentsOfURL:error:]

In the first case, the allocated AVAudioPlayer instance retains the passed in NSData. In the second, the passed in NSURL is retained:

I've attached some screen shots of the Instruments window showing the retain/release history for a passed in NSData object.

You can see the AVAudioPlayer object then creates a C++ object AVAudioPlayerCpp, which retains the NSData again:

Later, when the AVAudioPlayer object is released, the NSData is released, but there's never a release call from the associated AVAudioPlayerCpp... (You can tell from the attached image)

Seems you'll have to use a different solution to play media if you want to avoid leaking NSData/NSURL's..

Here's my test code:

-(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;
}

As per the previous answers, all of my own research and the discussions in the apple developer forums points to the problem being present in Apple's own libraries in iOS versions 6.0, 6.0.1 and as far as I can tell 6.0.2 as well.

iOS 6.1 has fixed this issue and I can no longer see the leaks any more.

Sadly, this means that if you believe that your App will be run on phones still running versions 6.0, 6.0.1 or 6.0.2 of iOS, you will have to do workarounds to remedy the leaks for these cases.

What can be good to know is that the retainCount for whatever has been used to initialize the AVAudioPlayer seems to be 1 if everything else has been cleared as intended.

My own workaround is to encapsulate the audio playing in its own class, for which I do manual memory handling using the -fno-objc-arc preprocessor flag when compiling.

When it comes to the time to release everything I make sure to fetch the retain count of the NSURL which I've used for initialization (will work the same if initialized with NSData) before actually commencing the release of it.

If everything else is handled correctly, by now it should be either 1 or 2, so simply release it the right amount of times.

Also, make sure to fetch the retain count before starting the release, otherwise you will be trying to access a freed object on iOS versions where the bug has been fixed.

This is a problem with the Apple library itself. Many of the posts (on stackoverflow also) have reported the similar problem. There's nothing you can do here.

It is my understanding that when working in ARC projects, you as the developer are no longer in charge of retains / releases, therefore the issue is with Apple's library and not your code.

Jesse Black

If this is truly a bug in Apple's Library, here is a not so fun workaround.

For the class in question, make it so that it does not have arc enabled.

This can be done by going to the target's build phases.
Go to the compile sources drop down and find the .m file for your class. enter -fno-objc-arc compiler flags column

Unfortunately, you will need to adjust the class and manually manage memory.

A more detailed description for configuring a class to not be arc enabled was on this answer on another question on SO

EDIT

I suppose you could encapsulate the AVAudioPlayer behavior to another class, and use that class for all your playback. In that case, you could set -fno-objc-arc for the one file and not have to work on the memory management of your entire app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!