【iOS】讓NSLog打印字典顯示得更好看(解決中文亂碼并顯示成JSON格式)

前言

文章的初衷很簡單,是為了能夠正常顯示打印出字典里面的中文。因為默認(rèn)情況下,直接打印字典的話,在Xcode控制臺上,中文會是亂碼的,需要Unicode轉(zhuǎn)碼才能看到中文。
比如打印下面的一個字典

NSDictionary *dict = @{
                       @"ArticleTitle":@"【iOS開發(fā)】打開另一個APP(URL Scheme與openURL)",
                       @"ArticleUrl":@"http://m.itdecent.cn/p/0811ccd6a65d",
                       @"author":@{
                               @"nickName":@"謙言忘語",
                               @"blog":@"http://m.itdecent.cn/u/cc2cf725ac0c",
                               @"work":@"iOS工程師"
                               }
                       };
NSLog(@"打印出的字典:%@",dict);

Xcode控制臺上顯示的是這樣子的:


默認(rèn)情況下Xcode打印字典,中文會顯示亂碼

WTF!誰能告訴我,這坨東西是什么玩意兒??。?!

其實還是可以知道這些Unicode編碼是什么意思的。平常我遇到這種情況會復(fù)制那堆Unicode的代碼到在線網(wǎng)站上進(jìn)行轉(zhuǎn)碼查看。但是依然覺得不太方便。

使用在線網(wǎng)站進(jìn)行Unicode轉(zhuǎn)碼

先看看結(jié)果

我終于無法忍受這么坑爹的中文顯示了,查找一些資料、經(jīng)過一系列嘗試之后,終于找到一個比較滿意的解決方案了。先看結(jié)果:


最終結(jié)果
2018-09-03 15:43:10.046 PrintBeautifulLog[4446:1265987] 打印出的字典:{
  "ArticleTitle" : "【iOS開發(fā)】打開另一個APP(URL Scheme與openURL)",
  "ArticleUrl" : "https:\/\/m.itdecent.cn\/p\/0811ccd6a65d",
  "author" : {
    "work" : "iOS工程師",
    "blog" : "https:\/\/m.itdecent.cn\/u\/cc2cf725ac0c",
    "nickName" : "謙言忘語"
  }
}

是不是頓時覺得神清氣爽?中文出來了,而且格式也很好看,層次分明。
對了,是不是覺得這個格式似曾相似?
嘿嘿,沒錯,這個就是JSON格式。不信?我們拿去JSON在線格式化網(wǎng)站上驗證下?

JSON格式驗證

另外,使用po命令調(diào)試打印的時候也是一樣的。
po命令調(diào)試時也能打印打印出JSON格式的Log

直接將文件拖入到工程中即可使用

這么神奇的效果?怎么做到的?嗯,很簡單,直接將github倉庫上的這兩個分類拉入到工程中就可以了。什么代碼都不用寫。

直接將這兩個分類拉入到工程中即可使用

怎么做到的?

其實代碼很簡單,簡單到難以想象。分類里面就只有10多行代碼。

//NSDictionry分類實現(xiàn)文件代碼
#import "NSDictionary+Log.h"
@implementation NSDictionary (Log)
#ifdef DEBUG
//打印到控制臺時會調(diào)用該方法
- (NSString *)descriptionWithLocale:(id)locale{
    return self.debugDescription;
}
//有些時候不走上面的方法,而是走這個方法
- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level{
    return self.debugDescription;
}
//用po打印調(diào)試信息時會調(diào)用該方法
- (NSString *)debugDescription{
    NSError *error = nil;
    //字典轉(zhuǎn)成json
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self options:NSJSONWritingPrettyPrinted  error:&error];
    //如果報錯了就按原先的格式輸出
    if (error) {
        return [super debugDescription];
    }
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    return jsonString;
}
#endif
@end

接下來解釋下這段代碼:

  • NSLog打印字典(NSDictionary)和數(shù)組(NSArray)的時候的時候會走- (NSString *)descriptionWithLocale:(id)locale來決定打印的字符串。打印其他對象(比如NSString類型)的時候會走- (NSString *)description方法。所以現(xiàn)在我們需要重寫NSDictionary的- (NSString *)descriptionWithLocale:(id)locale方法來得到我們想要的結(jié)果。
  • 在使用po命令調(diào)試的時候,會走- (NSString *)debugDescription方法,所以我們需要覆蓋該方法來顯示出我們想要的結(jié)果。
  • - (NSString *)descriptionWithLocale:(id)locale- (NSString *)debugDescription方法里面將字典轉(zhuǎn)化為JSON字符串輸入,就能同時在代碼調(diào)試打印和使用po命令調(diào)試打印時都能得到我們想要的結(jié)果。
    NSError *error = nil;
    //字典轉(zhuǎn)成json格式字符串
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self options:NSJSONWritingPrettyPrinted  error:&error];
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    return jsonString;
  • 字典轉(zhuǎn)化成字符串有可能會失敗,所以失敗的時候我們就以默認(rèn)的格式輸出。
if (error) {
    return [super debugDescription];
}
  • 在分類里面做了DEBUG預(yù)編譯判斷,只有在DEBUG模式下才會調(diào)用該方法,線上包(線上包采用Release模式)不會受到影響。
#ifdef DEBUG
//分類中的代碼
#endif

嗯,NSArray分類里面的代碼也是一毛一樣的。所以打印NSArray也能像NSDictionary一樣使用JSON格式輸出,并且可以正常顯示中文。不多說了。

除了 - (NSString *)descriptionWithLocale:(id)locale方法之外,還有一個- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level方法。這兩個方法功能是一樣的,后者多了一個indent(縮進(jìn))參數(shù)。我測試過這兩個方法的優(yōu)先級,發(fā)現(xiàn)前后測試的結(jié)果有點矛盾,所以就懶得理,兩個都實現(xiàn)了。

再看下其他解決NSLog打印字典時中文顯示亂碼的方式

還有其他的方式也能解決NSLog打印字典時顯示亂碼的問題。方法是一樣的,增加字典和數(shù)組的分類,重寫- (NSString *)descriptionWithLocale:(id)locale- (NSString *)debugDescription方法,修改Xcode輸出字符串。不同之處在于輸出字符串的處理方式。先看看常用的方式。

//NSDictionary分類實現(xiàn)文件代碼
- (NSString *)descriptionWithLocale:(id)locale{
    return self.debugDescription;
}
- (NSString *)debugDescription {
    NSMutableString *strM = [NSMutableString stringWithString:@"{\n"];
    [self enumerateKeysAndObjectsUsingBlock:^(id key,id obj,BOOL *stop) {
        [strM appendFormat:@"\t%@ = %@;\n", key, obj];
    }];
    [strM appendString:@"}\n"];
    return strM;
}
//NSArray分類實現(xiàn)文件代碼
- (NSString *)descriptionWithLocale:(id)locale{
    return self.debugDescription;
}
- (NSString *)debugDescription
{
    NSMutableString *strM = [NSMutableString stringWithString:@"(\n"];
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx,BOOL *stop) {
        [strM appendFormat:@"\t%@,\n", obj];
    }];
    [strM appendString:@")"];
    return strM;
}

這種方式是直接遍歷字典中的key和value,中間加一個=拼接起來。然后所有的key/value對拼接成一個字符串。每個key/value對后面都加入一個換行符\n。最后在前后加上大括號{}括起來。這種方式可以解決中文顯示亂碼的問題,但是有一個比較不好的地方,就是縮進(jìn)格式?jīng)]有了(Xcode默認(rèn)的格式是有縮進(jìn)格式的)。不管里面有多少層嵌套,前面都是一樣的間隔。在多層嵌套的時候看起來會不太爽。

遍歷key/value對,重新拼接輸出字符串

上面的方式無法處理縮進(jìn)格式問題,我們之前提過,使用- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level方法是有縮進(jìn)參數(shù)的,所以可以使用這個方法可以將縮進(jìn)格式搞出來??戳讼赂杏X還不錯。但是有個小缺點,使用po參數(shù)調(diào)試的時候就沒有辦法了。兩個方法分寫是在NSArray分類和NSDictionary分類里面實現(xiàn)的。代碼如下:

//NSArray
- (NSString *)descriptionWithLocale:(nullable id)locale indent:(NSUInteger)level{
    
    NSMutableString *mStr = [NSMutableString string];
    NSMutableString *tab = [NSMutableString stringWithString:@""];
    for (int i = 0; i < level; i++) {
        [tab appendString:@"\t"];
    }
    [mStr appendString:@"(\n"];
    for (int i = 0; i < self.count; i++) {
        NSString *lastSymbol = (self.count == i + 1) ? @"":@",";
        id value = self[i];
        if ([value respondsToSelector:@selector(descriptionWithLocale:indent:)]) {
            [mStr appendFormat:@"\t%@%@%@\n",tab,[value descriptionWithLocale:locale indent:level + 1],lastSymbol];
        } else {
            [mStr appendFormat:@"\t%@%@%@\n",tab,value,lastSymbol];
        }
    }
    [mStr appendFormat:@"%@)",tab];
    return mStr;
}
//NSDictionary
- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level
{
    NSMutableString *mStr = [NSMutableString string];
    NSMutableString *tab = [NSMutableString stringWithString:@""];
    for (int i = 0; i < level; i++) {
        [tab appendString:@"\t"];
    }
    [mStr appendString:@"{\n"];
    NSArray *allKey = self.allKeys;
    for (int i = 0; i < allKey.count; i++) {
        id value = self[allKey[i]];
        NSString *lastSymbol = (allKey.count == i + 1) ? @"":@";";
        if ([value respondsToSelector:@selector(descriptionWithLocale:indent:)]) {
            [mStr appendFormat:@"\t%@%@ = %@%@\n",tab,allKey[i],[value descriptionWithLocale:locale indent:level + 1],lastSymbol];
        } else {
[mStr appendFormat:@"\t%@%@ = %@%@\n",tab,allKey[i],value,lastSymbol];
        }
    }
    [mStr appendFormat:@"%@}",tab];
    return mStr;
}

還有另外一種方式,這種方式的思想是,上面第一種方式?jīng)]有縮進(jìn)格式,看起來很不爽,但是系統(tǒng)默認(rèn)的實現(xiàn)方式是有縮進(jìn)格式的。只是中文顯示有問題而已。那我直接把默認(rèn)方式中要輸出的字符串進(jìn)行Unicode轉(zhuǎn)化,將其轉(zhuǎn)化為中文不就可以了?
具體代碼就不貼了,有興趣可以看下這篇文章
這種方式確實可行,跟原先的輸出的唯一不同就是將Unicode字符串轉(zhuǎn)化為了中文字符串顯示。但是有一個缺點,那就是在將默認(rèn)方式的Unicode字符串轉(zhuǎn)化為中文字符串顯示的時候,容易出問題。因為轉(zhuǎn)碼之前是需要暴力替換的,這個替換過程是很容易出問題的。比如如果字典的value字符串里面本來就有" "符號,那轉(zhuǎn)碼就出問題了。

更新(20180914)

之前的方式遇到字典數(shù)組里面有模型的情況容易出問題。
于是在將字典/數(shù)組轉(zhuǎn)換成JSON字符串之前,先判斷其是否能轉(zhuǎn)換成JSON格式字符串,如果不能,就調(diào)用系統(tǒng)的原始實現(xiàn)。
由于要調(diào)用系統(tǒng)的原始實現(xiàn),所以還使用了method swizzle交換了上面說的3個系統(tǒng)方法。具體可查看github代碼。

更新(20230412)

在轉(zhuǎn)JSON的時候options里面增加了NSJSONWritingSortedKeysNSJSONWritingWithoutEscapingSlashes,前者可以將json讓key按照字母排序后輸出,便于查找。后者可以去除value里的轉(zhuǎn)義字符,看起來會更舒服。

//將obj轉(zhuǎn)換成json字符串。如果失敗則返回nil.
- (NSString *)convertToJsonString {
    
    //先判斷是否能轉(zhuǎn)化為JSON格式
    if (![NSJSONSerialization isValidJSONObject:self])  return nil;
    NSError *error = nil;
    
    NSJSONWritingOptions jsonOptions = NSJSONWritingPrettyPrinted;
    if (@available(iOS 11.0, *)) {
        //11.0之后,可以將JSON按照key排列后輸出,看起來會更舒服
        jsonOptions =  jsonOptions | NSJSONWritingSortedKeys;
    }
    if (@available(iOS 13.0,*)) {
        //13.0之后,可以去除Json里面的轉(zhuǎn)義字符
        jsonOptions =  jsonOptions | NSJSONWritingWithoutEscapingSlashes;
    }
    //核心代碼,字典轉(zhuǎn)化為有格式輸出的JSON字符串
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self options:jsonOptions  error:&error];
    if (error || !jsonData) return nil;
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    return jsonString;
}

參考

代碼已放在github上
iOS JSON數(shù)據(jù)NSLog小技巧
iOS 打印中文字典,數(shù)組,控制臺輸出中文,并保持縮進(jìn)格式
iOS description方法和descriptionWithLocale:方法 解決中文現(xiàn)問題
xcode8控制臺打印出字典和數(shù)組中的中文字符 解決中文亂碼
iOS開發(fā)實戰(zhàn)tips--讓Xcode的控制臺支持NSArray和NSDictionary的中文輸出
從NSDictionary打印不出中文開始

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容