歡迎光臨
每天分享高質量文章

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

作者:謙言忘語
鏈接:https://www.jianshu.com/p/79cd2476287d

前言

文章的初衷很簡單,是為了能夠正常顯示打印出字典裡面的中文。因為預設情況下,直接打印字典的話,在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:@"{ "];
    [self enumerateKeysAndObjectsUsingBlock:^(id key,id obj,BOOL *stop) {
        [strM appendFormat:@" %@ = %@; ", key, obj];
    }];
    [strM appendString:@"} "];
    return strM;
}

//NSArray分類實現檔案代碼
- (NSString *)descriptionWithLocale:(id)locale{
    return self.debugDescription;
}
- (NSString *)debugDescription
{
    NSMutableString *strM = [NSMutableString stringWithString:@"( "];
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx,BOOL *stop) {
        [strM appendFormat:@" %@, ", obj];
    }];
    [strM appendString:@")"];
    return strM;
}

這種方式是直接遍歷字典中的key和value,中間加一個=拼接起來。然後所有的key/value對拼接成一個字串。每個key/value對後面都加入一個換行符。最後在前後加上大括號{}括起來。這種方式可以解決中文顯示亂碼的問題,但是有一個比較不好的地方,就是縮進格式沒有了(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         [tab appendString:@" "];
    }
    [mStr appendString:@"( "];
    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:@" %@%@%@ ",tab,[value descriptionWithLocale:locale indent:level + 1],lastSymbol];
        } else {
            [mStr appendFormat:@" %@%@%@ ",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         [tab appendString:@" "];
    }
    [mStr appendString:@"{ "];
    NSArray *allKey = self.allKeys;
    for (int i = 0; i         id value = self[allKey[i]];
        NSString *lastSymbol = (allKey.count == i + 1) ? @"":@";";
        if ([value respondsToSelector:@selector(descriptionWithLocale:indent:)]) {
            [mStr appendFormat:@" %@%@ = %@%@ ",tab,allKey[i],[value descriptionWithLocale:locale indent:level + 1],lastSymbol];
        } else {
[mStr appendFormat:@" %@%@ = %@%@ ",tab,allKey[i],value,lastSymbol];
        }
    }
    [mStr appendFormat:@"%@}",tab];
    return mStr;
}


還有另外一種方式,這種方式的思想是,上面第一種方式沒有縮進格式,看起來很不爽,但是系統預設的實現方式是有縮進格式的。只是中文顯示有問題而已。那我直接把預設方式中要輸出的字串進行Unicode轉化,將其轉化為中文不就可以了?

具體代碼就不貼了,有興趣可以看下這篇文章https://www.jianshu.com/p/040293327e18

這種方式確實可行,跟原先的輸出的唯一不同就是將Unicode字串轉化為了中文字串顯示。但是有一個缺點,那就是在將預設方式的Unicode字串轉化為中文字串顯示的時候,容易出問題。因為轉碼之前是需要暴力替換的,這個替換過程是很容易出問題的。比如如果字典的value字串裡面本來就有" "符號,那轉碼就出問題了。

更新

之前的方式遇到字典陣列裡面有模型的情況容易出問題。


於是在將字典/陣列轉換成JSON字串之前,先判斷其是否能轉換成JSON格式字串,如果不能,就呼叫系統的原始實現。


由於要呼叫系統的原始實現,所以還使用了method swizzle交換了上面說的3個系統方法。具體可查看github代碼。

參考

  • 代碼已放在github上https://github.com/shixueqian/PrintBeautifulLog

  • iOS JSON資料NSLog小技巧https://www.jianshu.com/p/b6bb983e39da

  • iOS 打印中文字典,陣列,控制台輸出中文,並保持縮進格式https://www.jianshu.com/p/040293327e18

  • iOS description方法和descriptionWithLocale:方法 解決中文現問題https://blog.csdn.net/u011146511/article/details/51234462

  • xcode8控制台打印出字典和陣列中的中文字符 解決中文亂碼https://www.jianshu.com/p/294debd013d4

  • iOS開發實戰tips–讓Xcode的控制台支持NSArray和NSDictionary的中文輸出https://blog.csdn.net/biggercoffee/article/details/50094967

  • 從NSDictionary打印不出中文開始https://www.jianshu.com/p/f14b4cb1435b



編號315,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

更多推薦18個技術類微信公眾號

涵蓋:程式人生、演算法與資料結構、黑客技術與網絡安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

赞(0)

分享創造快樂