While writing some code to implement a block-based callback on top of some classes which are still stuck in the target-action paradigm, I stumbled across this rather nice little trick for doing so in a category without having to do any memory management tricks at all in retain-release mode. This example comes from adding a block callback to iOS’s UIBarButtonItem (DR is for Dark Rainfall, of course):
// This category is required, but doesn't have to be in the same place. // The concept is simple, and used by several other code libraries: Because blocks are // also objects when the Objective-C runtime is loaded, a category on NSObject adds // the selector to them. Obviously it would be foolish to call this category on an object // that was not a block. @interface NSObject (DRBlockCallback) - (void)DRcallbackBlock; - (void)DRcallbackBlockWithSender:(id)sender; @end @implementation NSObject (DRBlockCallback) - (void)DRcallbackBlock { ((void (^)())self)(); } - (void)DRcallbackBlockWithSender:(id)sender { ((void (^)(id))self)(sender); } @end
// Now for adding a block callback... @interface UIBarButtonItem (DRButtonBlockCallback) - (id)initWithBarButtonSystemItem:(UIBarButtonSystemItem)item block:(void (^)(id))b; @end @implementation UIBarButtonItem (DRButtonBlockCallback) - (id)initWithBarButtonSystemItem:(UIBarButtonSystemItem)item block:(void (^)(id))b { // Call correct initializer. Provide the block callback as action, but no target yet if ((self = [self initWithBarButtonSystemItem:item target:nil action:@selector(DRcallbackBlockWithSender:)])) { // It's necessary to copy the block and hang on to it somewhere. Categories can not add // ivars to a class. Associated references were designed to solve exactly this kind of // problem. This call associates the block with this object, conveniently doing the needed // copy as well. objc_setAssociatedObject(self, "DRactionBlock", b, OBJC_ASSOCIATION_COPY_NONATOMIC); // Now when the block is retrieved back from the association, the copy is returned, which is // the target for the block callback action. self.target = objc_getAssociatedObject(self, "DRactionBlock"); } return self; } // There is no need to override dealloc. The association will automatically be dissolved when the // object is released, at which point the copied block will also be released. @end
Poof. Proper management of a block’s memory in a retain-release environment without any effort! This little trick could be further simplified by another method on NSObject, something like - (id)associateBlock:(void (^)(id))b withKey:(const char *)key;, but that’s overkill in my opinion. For completeness’ sake, I also wrapped the whole thing in #if NS_BLOCKS_AVAILABLE, though on iOS that’s probably unnecessary, as I have no intention of ever developing for <4.0 again, and even on OS X I would use PLBlocks if I had to target Leopard.
For those waiting for Missions of the Reliant news, I beg your patience. I have not forgotten!