5

看到以下 iOS 开发建议并不少见:

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__);
    });
});

这很好,但是当出现问题时很难调试。查看输出:

AppName[1051:4013] __47-[Classname methodName]_block_invoke_0 work
AppName[1051:907] __block_global_0 updateUI

第一个日志行包含类和方法名称,因此我们有希望追踪外部块中的问题(希望我们没有在该方法中定义很多块),但第二个日志行(来自内部块) ? 祝你好运,特别是如果你在你的应用程序中经常使用这种模式。

有没有办法给块命名以帮助我们在控制台输出和崩溃日志中识别它们的源位置?

4

3 回答 3

4

块一旦被复制,就会成为 NSBlock 的实例,这意味着我们可以使用运行时向它添加各种好东西。这是一个例子:

@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;
}

这是该程序的输出:

// 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>

我建议您将 beautifyBlockDescription 函数包装在一个宏中,以便对于发布代码它只返回块。

于 2013-03-31T10:37:08.867 回答
0

您可以做的一件事是使用函数而不是块,而dispatch_async_f不是dispatch_async. 但是,权衡很重要,因为您失去了块的内联代码性质,以及无需通过上下文指针对其进行编组即可捕获状态的能力。

您也可以在外部块之外声明内部块并将其存储在局部变量中。有点不简洁,但它会像外部块一样用封闭方法的名称标记它。

于 2013-01-01T02:45:33.413 回答
0

您可以尝试使用https://github.com/conradev/BlockTypeDescription来提高开发过程中日志的可读性。然而,这个库替换了私有 NSBlock 类上的一个方法,你不应该尝试在应用商店二进制文件中使用它,否则它可能会被拒绝。这意味着您无法改进从 Apple 获得的崩溃日志。

于 2013-03-31T12:56:29.747 回答