问题
This has been bugging me for a while. How do I counteract the ugly escaping that happens when dumping objects in the debugger with po foo
(or via NSLog
). I've tried numerous approaches to implementing -description
or -debugDescription
to no avail.
Given this simple class
@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
And contrived usage
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 };
We get nice output for f0
(lldb) po f0
<Foo: 0x8cbc410> {
next = "<null>";
value = 0;
}
Tolerable output for f1
(lldb) po f1
<Foo: 0x8cbc480> {
next = "<Foo: 0x8cbc410> {\n next = \"<null>\";\n value = 0;\n}";
value = 1;
}
And horrendous output for 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;
}
This gets hard to parse fast when debugging real world object hierarchies. I'm assuming there's some other trick I'm missing since dumping a similarly nested NSDictionary
NSDictionary* d0 = @{ @"value": @0, @"next": NSNull.null };
NSDictionary* d1 = @{ @"value": @1, @"next": d0 };
NSDictionary* d2 = @{ @"value": @2, @"next": d1 };
Maintains the indenting and avoids the escaping hell
(lldb) po d2
{
next = {
next = {
next = "<null>";
value = 0;
};
value = 1;
};
value = 2;
}
UPDATE
Switching to -debugDescription
and simply forwarding to the dictionary
@implementation Foo
- (NSString *)debugDescription {
return self.dict.debugDescription;
}
@end
loses the recursive output
(lldb) po f2
{
next = "<Foo: 0x8b70e20>";
value = 2;
}
Internally NSDictionary
must be relying on -description
which I'm not implementing in this example, only -debugDescription
. Switching to something like the following
@implementation Foo
- (NSString *)description {
return self.dict.description;
}
- (NSString *)debugDescription {
return self.dict.debugDescription;
}
@end
produces similarly bad output as well
(lldb) po f2
{
next = "{\n next = \"{\\n next = \\\"<null>\\\";\\n value = 0;\\n}\";\n value = 1;\n}";
value = 2;
}
回答1:
TL;DR;
Use NSContainers-PrettyPrint and carefully read the docs.
Long Answer
After much more searching I discovered the descriptionWithLocale:indent: method. As documented, I should've been able to implement this in my own classes to achieve the desired pretty-print formatting. However, after some failed attempts I found a similar SO question. Turns out descriptionWithLocale:indent:
only works if you subclass a Foundation container class because of "security concerns".
Unsatisfied with that approach I continued digging and found this radar but also a solution in NSContainers-PrettyPrint. After some trial and error I got things working decently well. (It's not on CocoaPods so you have to add it manually).
Once NSContainers-PrettyPrint is added you'll probably want JRSwizzle too. Then define DEBUGPRINT_ALL
and DEBUGPRINT_SWIZZLE
in your DEBUG targets preprocessor macros. Finally, you can implement your descriptionWithLocale:indent:
methods in terms of the fs_* helpers and best practices.
Using the same Foo
from my question as an example
@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
Would produce the following output given the same f0
, f1
and f2
instances
(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>;
}>;
}>;
}
The above descriptionWithLocale:indent:
could use some tweaking to reduce the excessive whitespace but it still beats the alternatives.
回答2:
A similar question (NSDictionary description formatting problem — treats structure like char data) suggested that NSArray
and NSDictionary
are in some way cheating and not calling -[NSObject respondsToSelector:@selector(descriptionWithLocale:indent:)
. This appears to be true.
To test this I created an NSProxy
which log all calls. The test was:
NSObject *proxy = [LoggingProxy proxyWithTarget:[[NSObject alloc] init]];
NSLog(@"%@", @[proxy]);
The results were:
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
In my real objects I added the method:
#if DEBUG
- (BOOL) isNSDictionary__
{
return YES;
}
#endif
NSArray
and NSDictionary
then behaved as you'd hope by calling -[NSObject respondsToSelector:@selector(descriptionWithLocale:indent:)
. Make sure that if you return YES
you really have implemented descriptionWithLocale:indent:
as it will be called with out checking it's really there.
Please remember to wrap this method in #if DEBUG
. You really don't want to be shipping this, but it seems fine for use in Xcode.
来源:https://stackoverflow.com/questions/21346738/nsdictionary-like-pretty-print-in-the-debugger-or-log