【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
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。