Blocks and Retained Variables
Lets say you want to download an image in a UIViewController. This should certainly be done in a background thread and the modern approach is to use blocks.
Blocks are great but not as easy to use as some people may think. Everybody should know about the fact that blocks retain their variables. So I decided to give you a very simple and straightforward example of what can happen while using blocks and how to avoid it. Please note that I’m using ARC and I decided to use dispatch_after to mimic the behavior of downloading an image.
//
// BlockViewController.m
// Blocks
//
// Created by Petr Pavlik on 7/8/12.
// Copyright (c) 2012 Petr Pavlik. All rights reserved.
//
#import "BlockViewController.h"
@interface BlockViewController ()
@property (nonatomic, strong) NSString* something;
@end
@implementation BlockViewController
@synthesize something;
- (void)dealloc {
NSLog(@"block controller deallocated");
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(@"about to fire the block");
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(@"block reached");
self.something = @"something";
});
}
@end
Now imagine the situation where you push this view controller and pop it before the block is fired. Following listing shows my output of the console.
2012-07-08 10:56:45.468 Blocks[10117:f803] about to fire the block 2012-07-08 10:56:50.527 Blocks[10117:f803] block reached 2012-07-08 10:56:50.528 Blocks[10117:f803] block controller deallocated
I popped the view controller pretty much immediately after it appeared, definitely before the block should have been executed. And yet the view controller was deallocated five seconds later, right after the block was executed. Now this is bad, imagine that you would be pushing and pulling the view controller again and again. The application would probably crash at some point because there will be to many live instances of the view controller.
This happened because of the line of code that I have highlighted. All variables that appear in a block gets retained and live at least as long as the block itself. You can imagine the block as an object that you pass to dispatch_after function. This function keeps a reference to the block until five seconds pass. Than it executes the block and drops the reference to it.
The solution is not to use self in the block. You can create a weak pointer to self and use it in the block.
//
// BlockViewController.m
// Blocks
//
// Created by Petr Pavlik on 7/8/12.
// Copyright (c) 2012 uLikeIT. All rights reserved.
//
#import "BlockViewController.h"
@interface BlockViewController ()
@property (nonatomic, strong) NSString* something;
@end
@implementation BlockViewController
@synthesize something;
- (void)dealloc {
NSLog(@"block controller deallocated");
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(@"about to fire the block");
__weak BlockViewController* weakSelf = self;
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(@"block reached");
if (!weakSelf) { NSLog(@"reference to self is nil"); }
weakSelf.something = @"something";
});
}
@end
Now the output from the console would look like this.
2012-07-08 11:00:05.209 Blocks[10152:f803] about to fire the block 2012-07-08 11:00:06.731 Blocks[10152:f803] block controller deallocated 2012-07-08 11:00:10.212 Blocks[10152:f803] block reached 2012-07-08 11:00:10.212 Blocks[10152:f803] reference to self is nil
As you can se, I popped the view controller like a second after it appeared and the view controller was immediately deallocated. The block was executed but the view controller had been already deallocated and the weak pointer to it was automatically set to nil (yeah, weak pointers are cool).
Hi, thanks for posting this. Your post helped me with cleaning memory before recreating a game scene. But I think that there is a typo in your sample. Shouldn’t there be an assignment ?:
__weak BlockViewController* weakSelf = self;
You’re right that was a typo. Thanks for your comment.