前言
文章的初衷很簡單肮韧,是為了能夠正常顯示打印出字典里面的中文。因為默認情況下塑崖,直接打印字典的話七冲,在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控制臺上顯示的是這樣子的:
WTF!誰能告訴我抒蚜,這坨東西是什么玩意兒掘鄙?!N怂琛操漠!
其實還是可以知道這些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在線格式化網站上驗證下其爵?
另外冒冬,使用po命令調試打印的時候也是一樣的伸蚯。
直接將文件拖入到工程中即可使用
這么神奇的效果摩渺?怎么做到的?嗯剂邮,很簡單摇幻,直接將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默認的格式是有縮進格式的)持舆。不管里面有多少層嵌套色瘩,前面都是一樣的間隔。在多層嵌套的時候看起來會不太爽吏廉。
上面的方式無法處理縮進格式問題,我們之前提過席覆,使用- (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里面增加了NSJSONWritingSortedKeys
和NSJSONWritingWithoutEscapingSlashes
,前者可以將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打印不出中文開始