从NSDictionary打印不出中文开始

作者: 卖萌凉 | 来源:发表于2015-12-27 22:23 被阅读8756次

    一直以来,我都被一个问题小小困扰,就是当我在lldb中想要查看一个NSDictionary对象时,其中的中文会显示成\Uxxxx
    比如我创建了一个NSDictionary对象:

    NSDictionary *dic = @{@"我" : @"哈哈"};
    

    当我在lldb中想要查看它时,我使用了po命令,但是打印出来却是这样:

    (lldb) po dic
    {
        "\U6211" = "\U54c8\U54c8";
    }
    

    虽然单独打印键和值都能显示出正确的中文,也不影响程序的最终执行结果,但是在调试的时候,没法方便直观的看到dic里的数据,还是有点苦恼的。

    之前也没怎么在意,不过秉承着(三分钟热度的)新年新气象的决心,打算解决一下这个问题。


    解决方案



    先说最后找到的一个解决方案:利用chisel中的pjson命令,就可以查看到NSDictionary对象中的中文了(=゚ω゚)ノ。

    (lldb) pjson dic
    {
      "我" : "哈哈"
    }
    



    除此之外,之前还考虑了几种解决办法:

    1. 利用method swizzling替换NSDictionary中的description方法:
      可以参考这篇博客:解决 NSDictionary 输出中文字符乱码(Unicode)问题,但是使用这个方法也有诸多问题,比如需要给每个工程加上这个扩展,替换系统方法存在一定风险。

    2. 在lldb上做手脚:
      我只是希望能在debug的时候让NSDictionary打印中文,并非想改变NSDictionary的实现,所以想到,在lldb上做手脚应该是一个比较合适的方法。
      前两天刚刚装了chisel,感觉在lldb上做手脚的方案应该可行,所以想先研究一下chisel是怎么工作的,然后发现用户其实可以在chisel中自定义命令。

    正在我研究chisel源码的时候,突然发现其中居然有个pjson命令(☆_☆),一试,原来正符合我的需要。
    虽然这个方法不能在NSLog的时候也正常显示NSDictionary对象中的中文,但是平时debug我基本都使用lldb上的命令,所以这个局限对我来说也没有什么影响。


    原理



    为什么用pjson就可以正确打印出NSDictionary对象中的中文呢?
    先看看chiselpjson命令的实现,在/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
    



    什么都不懂@_@,上网补充了一点知识:

    1. \Uxxxx是UTF-16的编码(第一个Unicode平面),比如欧元符(€)的编码为\U20ac
    2. 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.

    而苹果在一封邮件中,明确的提到了,NSDictionaryNSArray都会打印出“old-style ASCII property list”。虽然这封邮件的时间有点早,且description方法很容易随着iOS版本的升级而改动,但是至少,它还是正面解释了为什么NSDictionary打印不出中文。

    相关文章

      网友评论

      • 我的发:受教了,博主的专研精神值得学习,手动点赞
      • xXPzXj:nb,我这个问题从我开始学iOS到做后台一直没搞明白。。汗颜,神他妈的iOS
      • Forelax:而且 [NSString stringWithCString:[desc cStringUsingEncoding:NSUTF8StringEncoding] encoding:NSNonLossyASCIIStringEncoding] 这个方法在最开始的 desc 中字符同时包含非 ascii 和 unicode 码的字符时结果也不正确
        Forelax:比如 desc 如果是 {\\U54C8}哈 会返回 nil
      • Forelax:『第三行中,假装cstring就是一些bit位,将其转化为一个NSString对象,而不进行任何转码』这个我觉得不太对,按照文档的描述,NSNonLossyASCIIStringEncoding 是『7-bit verbose ASCII to represent all Unicode characters.』,我觉得意思应该是说,把所有的二进制位当做 ascii 码来解析,然后把这些 ascii 码的解析结果认为是 unicode 码来表示的,比如解析出来ascii 码的意思是 \U54c8,那就再转成『哈』。
      • muddytrack:我有段代码,printf可以打出中文,NSLog则不行。代码如下:
        NSString *str = @"真操蛋!";
        const char *cr = str.UTF8String;
        NSLog(@"char is %s\n",cr);

        打印出来是这个:
        char is 真操蛋!

        如果用printf就是好好的,不知道是为什么
      • 丶大明:你好,你现在用什么打印出控制台的中文—— chisel 的pjson一直打印不出来
      • EagleOne:7.3的xocode不支持chisel了
      • 37dad682ef20:也奇怪这个问题,谢谢你发文章。

        另外我也找到了一篇文章:http://blog.csdn.net/biggercoffee/article/details/50094967
        LuffyYa:我也看了这篇 简单易用。就是用runtime截获NSDictionary和NSArray的description和descriptionWithLocale:
        还有descriptionWithLocale:indent:这几个方法.并转成让Xcode支持中文的编码.
      • iStig:楼主 问一下 安装了 chisel 但是在xcode控制台打印 pjson dic的时候提示
        error: 'pjson' is not a valid command.
        error: Unrecognized command 'pjson' :smile: 没有这个命令行啊
        iStig:@卖萌凉 谢谢 以前无意识的早就安装过一次 所以这次提示我已经安装完 并没有出现下面如你说的提示 :blush:
        卖萌凉:@iStig 如果你是用brew来安装的话,安装完成的时候应该会看到类似这样一句话:
        Add the following line to ~/.lldbinit to load chisel when Xcode launches:
        command script import /usr/local/opt/chisel/libexec/fblldb.py
        所以需要把下面这行粘到~/.lldbinit里(如果没有这个文件就创建一下),然后重启Xcode
      • 半碗大米汤:看了之后怎么没有拨云见雾的感觉,虽然也一直被这个问题困扰。z z

      本文标题:从NSDictionary打印不出中文开始

      本文链接:https://www.haomeiwen.com/subject/uqbmhttx.html