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: Thanks Shani
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With