Block Retain Cycles
Blocks have retain counts. If you have retain counts, you can get retain cycles. Here is a typedef for a simple block, and a class that references the block, along with a string instance variable.
typedef void (^BoringBlock)(void); // The leaky object. @interface Leakzor : NSObject { NSString *_string; BoringBlock _blockhead; } // Print |string|. - (void) funk; @end // Leakzor
And here are -init and -dealloc.
- (id) init { if ((self = [super init])) { _string = @"snork"; // |string| is same as self->string, so |self| is retained. _blockhead = Block_copy(^{ NSLog (@"string is %@", _string); }); } return self; } // init - (void) dealloc { [_string release]; [_blockhead release]; [super dealloc]; } // dealloc
The _string instance variable is used in the block. Instance variable references like this are actually a pointer dereference off of self, so
NSLog (@"string is %@", _string);
is the same as
NSLog (@"string is %@", self->_string);
Because self is a variable in the outside scope that is being captured, it is retained. self will not be released until the block is released in -dealloc, but -dealloc will not be called until self has been released. This is the cause of the retain cycle.
You can break the retain cycle by using a __block-scoped local variable that points to self. __block-scoped objects are not retained automatically, so the retain cycle is not created:
- (id) init { if ((self = [super init])) { _string = @"snork"; // blockSelf is __block scope, so won't be auto-retained __block Leakzor *blockSelf = self; _blockhead = Block_copy(^{ NSLog (@"string is %@", blockSelf->_string); }); } return self; } // init
You do not have to jump through this hoop for every block you make because most blocks have a very short lifespan. Just be aware that if you have a block that has the same lifetime as its owning object and you are freeing the block at -dealloc time, you can get a retain cycle.