【问题标题】:NSDictionary-like pretty-print in the debugger (or log)调试器(或日志)中类似 NSDictionary 的漂亮打印
【发布时间】:2014-01-25 04:44:15
【问题描述】:

这已经困扰我一段时间了。我如何抵消使用po foo(或通过NSLog)在调试器中转储对象时发生的丑陋转义。我尝试了多种方法来实现-description-debugDescription 均无济于事。

鉴于这个简单的类

@interface Foo : NSObject
@property NSDictionary* dict;
@end

@implementation Foo
- (NSString *)description {
    // super.description for the <{classname} pointer> output
    return [NSString stringWithFormat:@"%@ %@", super.description, self.dict];
}
@end

和人为的用法

Foo* f0 = [[Foo alloc] init];
f0.dict = @{ @"value": @0, @"next": NSNull.null };
Foo* f1 = [[Foo alloc] init];
f1.dict = @{ @"value": @1, @"next": f0 };
Foo* f2 = [[Foo alloc] init];
f2.dict = @{ @"value": @2, @"next": f1 };

f0 得到了不错的输出

(lldb) po f0
<Foo: 0x8cbc410> {
    next = "<null>";
    value = 0;
}

f1 的可容忍输出

(lldb) po f1
<Foo: 0x8cbc480> {
    next = "<Foo: 0x8cbc410> {\n    next = \"<null>\";\n    value = 0;\n}";
    value = 1;
}

f2 的可怕输出

(lldb) po f2
<Foo: 0x8cbc4b0> {
    next = "<Foo: 0x8cbc480> {\n    next = \"<Foo: 0x8cbc410> {\\n    next = \\\"<null>\\\";\\n    value = 0;\\n}\";\n    value = 1;\n}";
    value = 2;
}

在调试现实世界的对象层次结构时,这很难快速解析。我假设自从转储类似嵌套的 NSDictionary 后,我还缺少其他一些技巧

NSDictionary* d0 = @{ @"value": @0, @"next": NSNull.null };
NSDictionary* d1 = @{ @"value": @1, @"next": d0 };
NSDictionary* d2 = @{ @"value": @2, @"next": d1 };

保持缩进,避免逃避地狱

(lldb) po d2
{
    next =     {
        next =         {
            next = "<null>";
            value = 0;
        };
        value = 1;
    };
    value = 2;
}

更新

切换到-debugDescription 并简单地转发到字典

@implementation Foo
- (NSString *)debugDescription {
    return self.dict.debugDescription;
}
@end

失去递归输出

(lldb) po f2
{
    next = "<Foo: 0x8b70e20>";
    value = 2;
}

在内部,NSDictionary 必须依赖于 -description,我在此示例中没有实现它,只有 -debugDescription。切换到类似下面的东西

@implementation Foo
- (NSString *)description {
    return self.dict.description;
}
- (NSString *)debugDescription {
    return self.dict.debugDescription;
}
@end

也会产生同样糟糕的输出

(lldb) po f2
{
    next = "{\n    next = \"{\\n    next = \\\"<null>\\\";\\n    value = 0;\\n}\";\n    value = 1;\n}";
    value = 2;
}

【问题讨论】:

  • 如果在 Foo description 方法中将 self.dict 替换为 [self.dict debugDescription] 会发生什么?
  • @rmaddy 遗憾的是它产生了相同的输出。这并不奇怪,因为-[NSDictionary description]-[NSDictionary debugDescription] 产生相同的输出。 NSArray 的情况并非如此,它会为 -debugDescription 产生更差的 IMO 输出。但是,NSArray 在这两种情况下仍然存在类似的转义问题。
  • 当您在调试器中执行po d2 时,它应该调用debugDescription。由于这似乎给出了很好的输出,我希望明确地调用它会有所帮助。如果你实现Foo debugDescription 并在你的对象上调用debugDescription 会怎样?
  • @rmaddy 是的,但默认情况下-debugDescription 只是委托给-description,除非明确覆盖。 NSArray 是我知道的这样一个地方,但 NSDictionary 没有。我将编辑我的问题以对此进行扩展。

标签: ios objective-c xcode debugging lldb


【解决方案1】:

TL;DR;

使用NSContainers-PrettyPrint 并小心使用readdocs

长答案

经过更多搜索,我发现了descriptionWithLocale:indent: 方法。如文件所述,我应该能够在自己的类中实现这一点,以实现所需的漂亮打印格式。然而,经过一些失败的尝试后,我找到了similar SO question。事实证明,descriptionWithLocale:indent: 仅在您出于“安全问题”而对 Foundation 容器类进行子类化时才有效。

对这种方法不满意,我继续挖掘并找到了radar,但在NSContainers-PrettyPrint 中也找到了解决方案。经过一些试验和错误,我得到了很好的工作。 (它不在 CocoaPods 上,所以您必须手动添加)。

添加 NSContainers-PrettyPrint 后,您可能也需要JRSwizzle。然后在您的 DEBUG 目标预处理器宏中定义 DEBUGPRINT_ALLDEBUGPRINT_SWIZZLE。最后,您可以根据fs_* helpersbest practices 实现您的descriptionWithLocale:indent: 方法。

以我的问题中相同的Foo 为例

@implementation Foo
- (NSString*)description
{
    return [NSString stringWithFormat:@"%@ %@", super.description, self.dict.description];
}

- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level
{
    NSString * indent = [NSString fs_stringByFillingWithCharacter:' ' repeated:fspp_spacesPerIndent*level];
    NSMutableString * str = [[NSMutableString alloc] init];
    [str fs_appendObjectStartWithIndentString:indent caller:self];
    [str appendString:[self.dict descriptionWithLocale:locale indent:level+1]];
    [str fs_appendObjectEnd];
    return str;
}
@end

在给定相同的 f0f1f2 实例的情况下会产生以下输出

(lldb) po f0
<Foo: 0x8a385c0> {
    value = 0;
    next = <null>;
}
(lldb) po f1
<Foo: 0x8a38630> {
    value = 1;
    next = <Foo:0x8a385c0        {
            value = 0;
            next = <null>;
        }>;
}
(lldb) po f2
<Foo: 0x8a38660> {
    value = 2;
    next = <Foo:0x8a38630        {
            value = 1;
            next = <Foo:0x8a385c0                {
                    value = 0;
                    next = <null>;
                }>;
        }>;
}

上述descriptionWithLocale:indent: 可以使用一些调整来减少过多的空白,但它仍然优于其他选择。

【讨论】:

    【解决方案2】:

    一个类似的问题 (NSDictionary description formatting problem — treats structure like char data) 表明 NSArrayNSDictionary 在某种程度上是作弊而不是打电话给 -[NSObject respondsToSelector:@selector(descriptionWithLocale:indent:)。这似乎是真的。

    为了测试这一点,我创建了一个 NSProxy 来记录所有呼叫。测试是:

    NSObject *proxy = [LoggingProxy proxyWithTarget:[[NSObject alloc] init]];
    NSLog(@"%@", @[proxy]);
    

    结果是:

    message: isNSString__
    message: isNSDictionary__
    message: isNSArray__
    message: isNSData__
    
    2014-05-29 15:29:22.728 Proxy[36219:303] (
        "<LoggingProxy: 0x100103510>"
    )
    Program ended with exit code: 0
    

    在我的真实对象中我添加了方法:

    #if DEBUG
    - (BOOL) isNSDictionary__
    {
        return YES;
    }
    #endif
    

    NSArrayNSDictionary 然后通过调用-[NSObject respondsToSelector:@selector(descriptionWithLocale:indent:) 表现得如您所愿。确保如果您返回 YES,您确实已经实现了 descriptionWithLocale:indent:,因为它会在不检查它是否真的存在的情况下被调用。

    请记住将此方法包装在 #if DEBUG中。你真的不想发布这个,但它似乎可以在 Xcode 中使用。

    【讨论】:

      猜你喜欢
      • 2021-11-06
      • 1970-01-01
      • 2021-06-22
      • 2021-10-10
      • 2012-01-11
      • 2022-07-30
      • 1970-01-01
      • 1970-01-01
      • 2011-03-23
      相关资源
      最近更新 更多