Breaking ARC retain cycle in Objective-C blocks
In a recent client project, we noticed its iOS app often received low memory warning. The iOS app is developed with ARC-enabled (Automatic Reference Counting).
When profiled the app using Instruments > Allocations, we found that a lot of unused ViewController objects were not released from memory.
Retain Cycle
The codebase uses a lot of Objective-C blocks as shown below, and
self
is often being called within the blocks:
@interface DetailPageViewController : UIViewController
@property(nonatomic, strong) UIButton *backButton;
...
@end
@implementation DetailPageViewController
@synthesize backButton;
- (void)loadView {
...
[self.doneButton onTouch:^(id sender) {
[self doSomething];
self.isDone = YES;
}];
}
...
@end
In this example code, the controller object holds a strong reference to
doneButton
object. But because doneButton onTouch:
block is calling
self
, now the button object holds a strong reference back to the controller
object.
When object A points strongly to object B, and B points strongly to A, a retain cycle is created, and both objects cannot be released from memory.
Use Lifetime Qualifiers
Apple Developer’s guide on ARC transition suggested a few ways to break the retain cycle using different lifetime qualifiers. It is definitely a must read for iOS development.
If your app is targeted for iOS 5, you can use __weak
lifetime qualifier to break the cycle:
@interface DetailPageViewController : UIViewController
- (void)loadView {
...
__weak DetailPageViewController *controller = self;
[self.doneButton onTouch:^(id sender) {
[controller doSomething];
controller.isDone = YES;
}];
}
@end
Because we are using __weak
qualifier, doneButton onTouch:
block
only has a weak reference to the controller object. Now, the controller
object can be released from memory when its reference count drops to 0.