1. 程式人生 > >【iOS】讓NSLog列印字典顯示得更好看(解決中文亂碼並顯示成JSON格式)

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

前言

文章的初衷很簡單,是為了能夠正常顯示打印出字典裡面的中文。因為預設情況下,直接列印字典的話,在Xcode控制檯上,中文會是亂碼的,需要Unicode轉碼才能看到中文。
比如列印下面的一個字典

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

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

 

預設情況下Xcode列印字典,中文會顯示亂碼

 

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

其實還是可以知道這些Unicode編碼是什麼意思的。平常我遇到這種情況會複製那堆Unicode的程式碼到線上網站上進行轉碼檢視。但是依然覺得不太方便。

使用線上網站進行Unicode轉碼

 

先看看結果

我終於無法忍受這麼坑爹的中文顯示了,查詢一些資料、經過一系列嘗試之後,終於找到一個比較滿意的解決方案了。先看結果:

 

最終結果

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

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

JSON格式驗證


另外,使用po命令除錯列印的時候也是一樣的。

po命令除錯時也能列印打印出JSON格式的Log

 

直接將檔案拖入到工程中即可使用

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

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

 

怎麼做到的?

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

//NSDictionry分類實現檔案程式碼
#import "NSDictionary+Log.h"
@implementation NSDictionary (Log)
#ifdef DEBUG
//列印到控制檯時會呼叫該方法
- (NSString *)descriptionWithLocale:(id)locale{
    return self.debugDescription;
}
//有些時候不走上面的方法,而是走這個方法
- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level{
    return self.debugDescription;
}
//用po列印除錯資訊時會呼叫該方法
- (NSString *)debugDescription{
    NSError *error = nil;
    //字典轉成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)和陣列(NSArray)的時候的時候會走- (NSString *)descriptionWithLocale:(id)locale來決定列印的字串。列印其他物件(比如NSString型別)的時候會走- (NSString *)description方法。所以現在我們需要重寫NSDictionary的- (NSString *)descriptionWithLocale:(id)locale方法來得到我們想要的結果。
  • 在使用po命令除錯的時候,會走- (NSString *)debugDescription方法,所以我們需要覆蓋該方法來顯示出我們想要的結果。
  • - (NSString *)descriptionWithLocale:(id)locale- (NSString *)debugDescription方法裡面將字典轉化為JSON字串輸入,就能同時在程式碼除錯列印和使用po命令除錯列印時都能得到我們想要的結果。
    NSError *error = nil;
    //字典轉成json格式字串
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self options:NSJSONWritingPrettyPrinted  error:&error];
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    return jsonString;
  • 字典轉化成字串有可能會失敗,所以失敗的時候我們就以預設的格式輸出。
if (error) {
    return [super debugDescription];
}
  • 在分類裡面做了DEBUG預編譯判斷,只有在DEBUG模式下才會呼叫該方法,線上包(線上包採用Release模式)不會受到影響。
#ifdef DEBUG
//分類中的程式碼
#endif

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

除了 - (NSString *)descriptionWithLocale:(id)locale方法之外,還有一個- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level方法。這兩個方法功能是一樣的,後者多了一個indent(縮排)引數。我測試過這兩個方法的優先順序,發現前後測試的結果有點矛盾,所以就懶得理,兩個都實現了。

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

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

//NSDictionary分類實現檔案程式碼
- (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分類實現檔案程式碼
- (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。最後在前後加上大括號{}括起來。這種方式可以解決中文顯示亂碼的問題,但是有一個比較不好的地方,就是縮排格式沒有了(Xcode預設的格式是有縮排格式的)。不管裡面有多少層巢狀,前面都是一樣的間隔。在多層巢狀的時候看起來會不太爽。

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

 

上面的方式無法處理縮排格式問題,我們之前提過,使用- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level方法是有縮排引數的,所以可以使用這個方法可以將縮排格式搞出來。看了下感覺還不錯。但是有個小缺點,使用po引數除錯的時候就沒有辦法了。兩個方法分寫是在NSArray分類和NSDictionary分類裡面實現的。程式碼如下:

//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;
}

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

更新(20180914)

之前的方式遇到字典數組裡面有模型的情況容易出問題。
於是在將字典/陣列轉換成JSON字串之前,先判斷其是否能轉換成JSON格式字串,如果不能,就呼叫系統的原始實現。
由於要呼叫系統的原始實現,所以還使用了method swizzle交換了上面說的3個系統方法。具體可檢視github程式碼。

參考

程式碼已放在github上
iOS JSON資料NSLog小技巧
iOS 列印中文字典,陣列,控制檯輸出中文,並保持縮排格式
iOS description方法和descriptionWithLocale:方法 解決中文現問題
xcode8控制檯打印出字典和陣列中的中文字元 解決中文亂碼
iOS開發實戰tips--讓Xcode的控制檯支援NSArray和NSDictionary的中文輸出
從NSDictionary打印不出中文開始

 



作者:謙言忘語
連結:https://www.jianshu.com/p/79cd2476287d
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。