Search code examples
iosmemory-leaksinstrumentsnsurl

Leak from NSURL and AVAudioPlayer using ARC


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


Solution

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