Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Leak from NSURL and AVAudioPlayer using ARC

Tags:

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

like image 562
shannoga Avatar asked Sep 19 '12 15:09

shannoga


2 Answers

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;
}
like image 52
nielsbot Avatar answered Oct 02 '22 23:10

nielsbot


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.

like image 26
Magebarf Avatar answered Oct 02 '22 23:10

Magebarf