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

前言

文章的初衷很簡單肮韧,是為了能夠正常顯示打印出字典里面的中文。因為默認情況下塑崖,直接打印字典的話七冲,在Xcode控制臺上,中文會是亂碼的规婆,需要Unicode轉碼才能看到中文澜躺。
比如打印下面的一個字典

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

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


默認情況下Xcode打印字典,中文會顯示亂碼

WTF!誰能告訴我抒蚜,這坨東西是什么玩意兒掘鄙?!N怂琛操漠!

其實還是可以知道這些Unicode編碼是什么意思的。平常我遇到這種情況會復制那堆Unicode的代碼到在線網站上進行轉碼查看饿这。但是依然覺得不太方便浊伙。

使用在線網站進行Unicode轉碼

先看看結果

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


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

是不是頓時覺得神清氣爽串结?中文出來了哑子,而且格式也很好看,層次分明肌割。
對了卧蜓,是不是覺得這個格式似曾相似?
嘿嘿把敞,沒錯弥奸,這個就是JSON格式。不信奋早?我們拿去JSON在線格式化網站上驗證下其爵?

JSON格式驗證

另外冒冬,使用po命令調試打印的時候也是一樣的伸蚯。
po命令調試時也能打印打印出JSON格式的Log

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

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

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

怎么做到的绰姻?

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

//NSDictionry分類實現(xiàn)文件代碼
#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)和數(shù)組(NSArray)的時候的時候會走- (NSString *)descriptionWithLocale:(id)locale來決定打印的字符串憨栽。打印其他對象(比如NSString類型)的時候會走- (NSString *)description方法帜矾。所以現(xiàn)在我們需要重寫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(縮進)參數(shù)是晨。我測試過這兩個方法的優(yōu)先級肚菠,發(fā)現(xiàn)前后測試的結果有點矛盾,所以就懶得理罩缴,兩個都實現(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。最后在前后加上大括號{}括起來圃郊。這種方式可以解決中文顯示亂碼的問題价涝,但是有一個比較不好的地方,就是縮進格式沒有了(Xcode默認的格式是有縮進格式的)持舆。不管里面有多少層嵌套色瘩,前面都是一樣的間隔。在多層嵌套的時候看起來會不太爽吏廉。

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

上面的方式無法處理縮進格式問題,我們之前提過席覆,使用- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level方法是有縮進參數(shù)的史辙,所以可以使用這個方法可以將縮進格式搞出來∨迳耍看了下感覺還不錯聊倔。但是有個小缺點,使用po參數(shù)調試的時候就沒有辦法了生巡。兩個方法分寫是在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;
}

還有另外一種方式,這種方式的思想是孤荣,上面第一種方式沒有縮進格式甸陌,看起來很不爽,但是系統(tǒng)默認的實現(xiàn)方式是有縮進格式的盐股。只是中文顯示有問題而已钱豁。那我直接把默認方式中要輸出的字符串進行Unicode轉化,將其轉化為中文不就可以了疯汁?
具體代碼就不貼了牲尺,有興趣可以看下這篇文章
這種方式確實可行,跟原先的輸出的唯一不同就是將Unicode字符串轉化為了中文字符串顯示幌蚊。但是有一個缺點谤碳,那就是在將默認方式的Unicode字符串轉化為中文字符串顯示的時候溃卡,容易出問題。因為轉碼之前是需要暴力替換的蜒简,這個替換過程是很容易出問題的瘸羡。比如如果字典的value字符串里面本來就有" "符號,那轉碼就出問題了臭蚁。

更新(20180914)

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

更新(20230412)

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

//將obj轉換成json字符串。如果失敗則返回nil.
- (NSString *)convertToJsonString {
    
    //先判斷是否能轉化為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里面的轉義字符
        jsonOptions =  jsonOptions | NSJSONWritingWithoutEscapingSlashes;
    }
    //核心代碼进统,字典轉化為有格式輸出的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ù)組,控制臺輸出中文,并保持縮進格式
iOS description方法和descriptionWithLocale:方法 解決中文現(xiàn)問題
xcode8控制臺打印出字典和數(shù)組中的中文字符 解決中文亂碼
iOS開發(fā)實戰(zhàn)tips--讓Xcode的控制臺支持NSArray和NSDictionary的中文輸出
從NSDictionary打印不出中文開始

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末助币,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子螟碎,更是在濱河造成了極大的恐慌眉菱,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掉分,死亡現(xiàn)場離奇詭異俭缓,居然都是意外死亡,警方通過查閱死者的電腦和手機酥郭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門华坦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人褥民,你說我怎么就攤上這事季春。” “怎么了消返?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵载弄,是天一觀的道長耘拇。 經常有香客問我,道長宇攻,這世上最難降的妖魔是什么惫叛? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮逞刷,結果婚禮上嘉涌,老公的妹妹穿的比我還像新娘。我一直安慰自己夸浅,他們只是感情好仑最,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帆喇,像睡著了一般警医。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坯钦,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天预皇,我揣著相機與錄音,去河邊找鬼婉刀。 笑死吟温,一個胖子當著我的面吹牛,可吹牛的內容都是我干的突颊。 我是一名探鬼主播鲁豪,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洋丐!你這毒婦竟也來了呈昔?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤友绝,失蹤者是張志新(化名)和其女友劉穎堤尾,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迁客,經...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡郭宝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了掷漱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粘室。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖卜范,靈堂內的尸體忽然破棺而出衔统,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布锦爵,位于F島的核電站舱殿,受9級特大地震影響,放射性物質發(fā)生泄漏险掀。R本人自食惡果不足惜沪袭,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望樟氢。 院中可真熱鬧冈绊,春花似錦、人聲如沸埠啃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霸妹。三九已至十电,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叹螟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工台盯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留罢绽,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓静盅,卻偏偏與公主長得像良价,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蒿叠,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容