Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

naming blocks in objective-c?

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?

like image 844
Jaysen Marais Avatar asked Oct 05 '22 11:10

Jaysen Marais


1 Answers

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.

like image 166
aLevelOfIndirection Avatar answered Oct 10 '22 03:10

aLevelOfIndirection