It is not uncommon to see iOS development advice along the lines of:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// work in background
NSLog(@"%s work", __PRETTY_FUNCTION__);
dispatch_async(dispatch_get_main_queue(), ^{
// update UI on main queue
NSLog(@"%s updateUI", __PRETTY_FUNCTION__);
});
});
This is great, but it can be tough to debug when something goes wrong. Looking at the output:
AppName[1051:4013] __47-[Classname methodName]_block_invoke_0 work
AppName[1051:907] __block_global_0 updateUI
The first log line has the class and method names so we have some hope of tracking down an issue in the outer block (hopefully we haven't defined many blocks in that method), but the second log-line (from the inner block)? Good luck, especially if you've used this pattern a lot in your app.
Is there a way to give blocks names which will help us identify their source locations in console output and crash logs?
Blocks, once copied, become instances of NSBlock, which means that we can then use the runtime to add all sorts of nice things to it. Here is an example:
@protocol QuietTheCompiler<NSObject>
- (NSString*) prettyBlockDescription;
@end
static id beautifyBlockDescription(id block, NSString *name)
{
static void *kAssocObjectPrettyBlockDescription = "A";
Class blockClass = [block class];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL descrSel = @selector(description);
SEL prettySel = @selector(prettyBlockDescription);
Method descrMethod = class_getInstanceMethod(blockClass, descrSel);
IMP originalImpl = class_getMethodImplementation(blockClass, descrSel);
IMP prettyImpl = imp_implementationWithBlock(^(id self_) {
id value = objc_getAssociatedObject(self_, kAssocObjectPrettyBlockDescription);
return (value != nil)? value : originalImpl(self_, descrSel);
});
if (class_addMethod(blockClass, prettySel, prettyImpl, method_getTypeEncoding(descrMethod))) {
IMP newImpl = imp_implementationWithBlock(^(id self_) {
return [self_ prettyBlockDescription];
});
class_replaceMethod(blockClass, descrSel, newImpl, method_getTypeEncoding(descrMethod));
}
});
NSString *description = [NSString stringWithFormat:@"<%@: %p name=%@>",NSStringFromClass(blockClass),block,name];
objc_setAssociatedObject(block, kAssocObjectPrettyBlockDescription, description, OBJC_ASSOCIATION_RETAIN);
return block;
}
int main (int argc, const char * argv[])
{
@autoreleasepool {
int (^block1)(int,NSString*) = ^(int i, NSString *fmt) {
return i;
};
id blockObject1 = beautifyBlockDescription([block1 copy], @"Hello");
int (^block2)(int,NSString*) = ^(int i, NSString *fmt) {
return i+1;
};
id blockObject2 = [block2 copy];
NSLog(@"Block 1: %@", blockObject1);
NSLog(@"Block 1: %@", blockObject2);
}
return 0;
}
Here is the output of this program:
// Sample Output
2013-03-31 12:34:48.984 Dummy[1231:303] Block 1: <__NSGlobalBlock__: 0x1000059d0 name=Hello>
2013-03-31 12:34:48.987 Dummy[1231:303] Block 1: <__NSGlobalBlock__: 0x100005a10>
I would suggest that you wrap the beautifyBlockDescription function in a macro so that for release code it just returns the block.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With