I wrote a method to add blocks based completion handler support to AVAudioPlayer. The calling code looks like this.

__block MKAudioPlayer *player = [[MKAudioPlayer alloc] initWithResourceName:@"Test.mp3"];
[player playWithCompletionHandler:^{
	player = nil;

The playWithCompletionHandler method naively copies the block into a property like this.

-(void) playWithCompletionHandler:(VoidBlock) block {
    self.audioCompletedBlock = block;
    [self.thePlayer play];

Smell something? No? Not yet?

Let’s consider the other case where the block is a property and you set the completion handler like this

__block MKAudioPlayer *player = [[MKAudioPlayer alloc] initWithResourceName:@"Test.mp3"];
player.completionHandler = ^{
	player = nil;
[player play];

In this case, LLVM compiler is clever enough to warn you. However, in the previous case, you end up with a retain cycle when the block copies the MKAudioPlayer reference within the playWithCompletionHandler method.

Neither the LLVM compiler nor the static analyzer warn you about this. And that doesn’t mean everything is fine. The first block of code leaks memory because of a retain cycle created when you copy the block.

BEWARE OF RETAIN CYCLES! With ARC, It just got a little more difficult to debug problems related to memory leaks.

I noted this problem a while ago when brianensorapps tweeted me a problem with my sample code and wrote this post after reading this tweet.


I seriously wish Apple improves the LLVM static analyzer to avoid true negatives and catch, if possible, all retain cycles.


Follow me on Twitter

  • Jason Larnx

    So, what kind of retain cycle is created in the first case? There is player and self.thePlayer..Could you please elaborate?