一直以来,我都被一个问题小小困扰,就是当我在lldb中想要查看一个NSDictionary
对象时,其中的中文会显示成\Uxxxx
。
比如我创建了一个NSDictionary
对象:
NSDictionary *dic = @{@"我" : @"哈哈"};
当我在lldb中想要查看它时,我使用了po
命令,但是打印出来却是这样:
(lldb) po dic
{
"\U6211" = "\U54c8\U54c8";
}
虽然单独打印键和值都能显示出正确的中文,也不影响程序的最终执行结果,但是在调试的时候,没法方便直观的看到dic
里的数据,还是有点苦恼的。
之前也没怎么在意,不过秉承着(三分钟热度的)新年新气象的决心,打算解决一下这个问题。
解决方案
先说最后找到的一个解决方案:利用chisel中的pjson
命令,就可以查看到NSDictionary
对象中的中文了(=゚ω゚)ノ。
(lldb) pjson dic
{
"我" : "哈哈"
}
除此之外,之前还考虑了几种解决办法:
-
利用method swizzling替换
NSDictionary
中的description
方法:
可以参考这篇博客:解决 NSDictionary 输出中文字符乱码(Unicode)问题,但是使用这个方法也有诸多问题,比如需要给每个工程加上这个扩展,替换系统方法存在一定风险。 -
在lldb上做手脚:
我只是希望能在debug的时候让NSDictionary
打印中文,并非想改变NSDictionary
的实现,所以想到,在lldb上做手脚应该是一个比较合适的方法。
前两天刚刚装了chisel,感觉在lldb上做手脚的方案应该可行,所以想先研究一下chisel是怎么工作的,然后发现用户其实可以在chisel中自定义命令。
正在我研究chisel源码的时候,突然发现其中居然有个pjson
命令(☆_☆),一试,原来正符合我的需要。
虽然这个方法不能在NSLog
的时候也正常显示NSDictionary
对象中的中文,但是平时debug我基本都使用lldb上的命令,所以这个局限对我来说也没有什么影响。
原理
为什么用pjson
就可以正确打印出NSDictionary
对象中的中文呢?
先看看chisel对pjson
命令的实现,在/commands/FBPrintCommands.py
中:
def run(self, arguments, options):
objectToPrint = arguments[0]
pretty = 1 if options.plain is None else 0
jsonData = fb.evaluateObjectExpression('[NSJSONSerialization dataWithJSONObject:{} options:{} error:nil]'.format(objectToPrint, pretty))
jsonString = fb.evaluateExpressionValue('(NSString*)[[NSString alloc] initWithData:{} encoding:4]'.format(jsonData)).GetObjectDescription()
print jsonString
虽然我对Python不太熟,但是大概能明白,在lldb中使用pjson
,相当于先将这个NSDictionary
对象序列化成NSData
对象,然后在转换成NSString
对象输出。
试了试用这种方法转换出的字符串,的确可以正确显示中文:
NSDictionary *dic = @{@"我" : @"哈哈"};
NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", jsonString);
2015-12-27 15:09:28.012 XSQPJsonNDemo[1796:1977106] {
"我" : "哈哈"
}
编码
虽然解决了这个问题,但是仍然对编码感觉很困惑。
解决 NSDictionary 输出中文字符乱码(Unicode)问题 中用了将NSString
转换成char *
再转换回NSString
的方法,为什么经过这两次转换就能让中文正确显示了呢?
@implementation NSDictionary (Unicode)
- (NSString*)my_description {
NSString *desc = [self my_description];
desc = [NSString stringWithCString:[desc cStringUsingEncoding:NSUTF8StringEncoding] encoding:NSNonLossyASCIIStringEncoding];
return desc;
}
@end
什么都不懂@_@,上网补充了一点知识:
-
\Uxxxx
是UTF-16的编码(第一个Unicode平面),比如欧元符(€)的编码为\U20ac
。 - NSString自身使用的是UTF-16:
An NSString object encodes a Unicode-compliant text string, represented as a sequence of UTF–16 code units. All lengths, character indexes, and ranges are expressed in terms of 16-bit platform-endian values, with index values starting at 0.
按照上面转换两次的思路,我写了这样几行代码:
NSString *string = @"\U20ac";
char *cstring = [string cStringUsingEncoding:NSUTF8StringEncoding];
NSString *trans = [[NSString alloc] initWithCString:cstring encoding:NSNonLossyASCIIStringEncoding];
第二行把\U20ac
转换成了一个char *
,这个char *
字符串使用的编码方式是UTF-8,而UTF-8中,英文字母和数字的编码和ASCII一致,故得到的char *
是这样的:
|char[0]|char[1]|char[2]|char[3]|char[4]|char[5]|char[6]|
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|''|'U'|'2'|'0'|'a'|'c'|\0|
|0x5c|0x55|0x32|0x30|0x61|0x63|0x00|
第三行中,假装cstring
就是一些bit位,将其转化为一个NSString
对象,而不进行任何转码。因为NSString
本身是使用的是UTF-16,故在它看来,这些bit位组合在一起,得到了@"€"
。
然后我又想,为什么第二行要选择UTF-8的编码方式呢?直接转成ASCII是不是也可以呢?
NSString *string = @"\\U20ac";
char *cstring = [string cStringUsingEncoding:NSASCIIStringEncoding];
NSString *trans = [[NSString alloc] initWithCString:cstring encoding:NSNonLossyASCIIStringEncoding];
试了一下,这样也能得到正确的结果。但是当一开始的string
中包含ASCII以外的字符时,cstring
就会为NULL
,执行第三行时崩溃。
抛开NSString
如果我只是单纯在写C代码,为什么运行下面这两行代码时,终端可以打印出中文?
char *chinese = "中文";
printf("%s", chinese);
这里面,chinese
只是一个字符数组,不包含任何编码信息,为什么最终打印的结果不是乱码呢?
运行到这里的时候,我查看了chinese
变量,发现其中存的已经是“中文”二字的UTF-8编码了。是谁定义由“UTF-8”作为编码方式呢?猜测应该是Xcode editor?
想到打印到终端和打印到文件的原理应该类似,如果输出到了文件,那么当我去查看这个文件的时候,这个文件本身有一个编码方式,如果编码方式和文件中的内容不符,则会看到乱码。那终端是不是也应该会有自己的编码方式?还真有。
OSX上的Terminal的“偏好设置”由于editor和终端都使用UTF-8的编码方式,所以在代码中的“中文”二字,打印到终端后能正确显示。
做了个小实验:
NSString *string = @"€";
char *cstring = [string cStringUsingEncoding:NSMacOSRomanStringEncoding];
printf("%s", cstring);
这里把欧元符转换成了Mac OS Roman的编码方式,存放入cstring
这个char *
字符串中,然后打印。如果终端为UTF-8编码,则打印出乱码,而换成Mac OS Roman编码后,则能正确打印欧元符。
参考
NSString
chisel
解决 NSDictionary 输出中文字符乱码(Unicode)问题
UTF-16
2016.3.20更新
想来这篇博客讲了这么多如何解决打印不出中文的问题,却依然没有提到,为什么NSDictionary
在输出到控制台的时候打印不出中文。
虽然我们不知道NSDictionary
究竟是怎么实现description
方法的,但是官方文档中好像给出了一点蛛丝马迹:
description
A string that represents the contents of the dictionary, formatted as a property list (read-only)
这里说到了property list。根据property list的文档,它可以被写作三种形式:XML、二进制和ASCII。浏览了一下它们的文档后,感觉ASCII格式与我们看到的、打印出来的NSDictionary
迷之相似。且在讲到用ASCII来表示NSString时,文档中提到:
Though the property list format uses ASCII for strings, note that Cocoa uses Unicode. Since string encodings vary from region to region, this representation makes the format fragile. You may see strings containing unreadable sequences of ASCII characters; these are used to represent Unicode characters.
而苹果在一封邮件中,明确的提到了,NSDictionary
和NSArray
都会打印出“old-style ASCII property list”。虽然这封邮件的时间有点早,且description
方法很容易随着iOS版本的升级而改动,但是至少,它还是正面解释了为什么NSDictionary
打印不出中文。
网友评论
NSString *str = @"真操蛋!";
const char *cr = str.UTF8String;
NSLog(@"char is %s\n",cr);
打印出来是这个:
char is 真操蛋!
如果用printf就是好好的,不知道是为什么
另外我也找到了一篇文章:http://blog.csdn.net/biggercoffee/article/details/50094967
还有descriptionWithLocale:indent:这几个方法.并转成让Xcode支持中文的编码.
error: 'pjson' is not a valid command.
error: Unrecognized command 'pjson' 没有这个命令行啊
Add the following line to ~/.lldbinit to load chisel when Xcode launches:
command script import /usr/local/opt/chisel/libexec/fblldb.py
所以需要把下面这行粘到~/.lldbinit里(如果没有这个文件就创建一下),然后重启Xcode