20
votes

I am runung Instruments on an iPhone 4S. I am using AVAudioPlayer inside this method:

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

I am getting leaks when playing the sound files:

Leaked objects:

1.

Object: NSURL

Responsible Library: Foundation

Responsable Frame: Foundation -[NSURL(NSURL) allocWithZone:]

2.

Object: _NSCFString

Responsible Library: Foundation

Responsable Frame: Foundation -[NSURL(NSURL) initFileURLWithPath:]

Instruments does not point directly to my code so I find it hard to locate the leak reason.

MY QUESTION

What could cause the leak? OR How can I locate leaks when I am not responsible to the code?

EDIT This is the schema from Instruments cycles view: enter image description here Thanks Shani

5
are you debugging with zombies enabled? that can cause objects to remain in memory...nielsbot
There's a bug in your code: You don't initialize error to nil, but you later test for !error. (APIs that take an NSError ** return are not guaranteed to set error unless the call fails. You must check audioPlayer != nil before using error. You should also initialize error to nil.nielsbot
I looked at it--seems to be a possible leak in Apple's code.. The AVAudioPlayer instance 1. retains the passed in data/url and 2. creates an instance of AVAudioPlayerCpp which 3) also retains the passed in data/url. When the AVAudioPlayer is released, it releases the data/url, but I never see a release from the associated AVAudioPlayerCppnielsbot
I posted the "proof". maybe someone wants to double check...nielsbot

5 Answers

19
votes

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.

enter image description here

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

enter image description here

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;
}
1
votes

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.

0
votes

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.

0
votes

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.

0
votes

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