[iOS開發(fā)必備]一個優(yōu)雅的打印框架:字典以JSON格式打印

緣起

我們在跟后端對接調(diào)試接口的時候,會將返回?cái)?shù)據(jù)轉(zhuǎn)成字典后打印出來。這時候我們發(fā)現(xiàn),Xcode控制臺輸出的格式并非JSON格式,跟后端同事溝通協(xié)作的時候不是特別方便。特別要吐槽的是打印出來的中文是Unicode編碼,這也太反人性了。于是我寫了一個框架,完美解決以上痛點(diǎn)。
比如后端下發(fā)了這樣的JSON數(shù)據(jù):

{
    "address": "我是云南的,云南麗江的",
    "info": {
        "blog": "http://www.itdecent.cn/u/399cc7c53fad",
        "isSingle": true,
        "nickName": "小而白",
        "score": 0.3
    },
    "name": "大魔王"
}

實(shí)際上,我們是以NSData類型(Objective-C)去接收網(wǎng)絡(luò)數(shù)據(jù)的。上述JSON字符串對應(yīng)的NSData,可通過如下方法轉(zhuǎn)換:

    NSString *jsonStr = @"{\
    \"address\": \"我是云南的,云南麗江的\",\
    \"info\": {\
        \"blog\": \"http://www.itdecent.cn/u/399cc7c53fad\",\
        \"isSingle\": true,\
        \"nickName\": \"小而白\",\
        \"score\": 0.3\
    },\
    \"name\": \"大魔王\"\
    }";
    NSData *jsonData = [jsonStr dataUsingEncoding:NSUTF8StringEncoding];

此時我們在本地模擬出了接收到的NSData數(shù)據(jù),將jsonData轉(zhuǎn)化成字典:

NSError *serializationError = nil;
NSDictionary *responseObject = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&serializationError];
//(AFNetWorking框架也是使用到這個方法)

debug調(diào)試responseObject信息截圖如下:


responseObject內(nèi)存信息.png

直接手寫構(gòu)造上面的字典:

NSDictionary *dict = @{
        @"name":@"大魔王",
        @"address":@"我是云南的,云南麗江的",
        @"info": @{
            @"nickName":@"小而白",
            @"blog":@"http://www.itdecent.cn/u/399cc7c53fad",
            @"score":@(0.3),
            @"isSingle":@(YES)
        }
    };
NSLog(@"字典:%@",dict);

Xcode控制臺上打印顯示:


默認(rèn)情況下打印字典.png

默認(rèn)的打印格式除了中文亂碼以外,還有兩個不容忽視的問題:
1、字典中@"isSingle":@(YES) BOOL類型的值被輸出為1
2、字典中@"score":@(0.3) 浮點(diǎn)數(shù)0.3被加上了引號變成了字符格式

解決方案

為了解決以上痛點(diǎn),我查閱了相關(guān)資料,寫了一個輕量級的零侵入打印框架 SZJsonLog

使用該框架后打印效果是這樣的


SZJsonLog打印字典.png

完美還原原始JSON數(shù)據(jù)。我已經(jīng)將該框架上傳到Github,您可以點(diǎn)擊 SZJsonLog源碼 下載,文件直接拖入工程,使用系統(tǒng)打印方法即可。祝您享用愉快!

如何實(shí)現(xiàn)?

其實(shí)很簡單,一句話就能說明白:依次取出字典中的鍵值對,進(jìn)行字符串拼接。最終輸出JSON格式的字符串。
NSLog打印字典(NSDictionary)和數(shù)組(NSArray)的時候會走- (NSString *)descriptionWithLocale:(id)locale來決定打印的字符串。所以現(xiàn)在我們在分類中重寫NSDictionary和NSArray(兩者可以相互嵌套)的- (NSString *)descriptionWithLocale:(id)locale方法來獲得我們預(yù)期的結(jié)果。
在使用po命令調(diào)試的時候,會走- (NSString *)debugDescription方法,我們同樣覆蓋該方法來實(shí)現(xiàn)預(yù)期效果。
至于零侵入,你們應(yīng)該想到了,就是利用runtime的方法交換,在編譯時注冊經(jīng)過改造的打印方法。
以NSDictionary示例,貼出部分代碼

@implementation NSDictionary (SZJsonLog)

- (NSString *)szlog_descriptionWithLocale:(id)locale {
    return [self descriptionWithLocale:locale indent:0];
}

- (NSString *)szlog_descriptionWithLocale:(id)locale indent:(NSUInteger)level {
    NSMutableString *desc = [NSMutableString string];
    NSMutableString *tabString = [[NSMutableString alloc] initWithCapacity:level];
    for (NSUInteger i = 0; i < level; ++i) {
        [tabString appendString:@"\t"];
    }
    NSString *tab = @"";
    if (level > 0) {
        tab = tabString;
    }
    [desc appendString:@"{\n"];
    // 對字典排序
    NSArray *allkeys = [self.allKeys sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        return [obj1 compare:obj2];
    }];
    
    for (id k in allkeys) {
        id obj = [self objectForKey:k];
        NSString *key = k;
        if ([key isKindOfClass:[NSString class]]) {
            key = [NSString stringWithFormat:@"\"%@\"", key];
        }
        if ([obj isKindOfClass:[NSString class]]) {
            [desc appendFormat:@"%@\t%@: \"%@\",\n", tab, key, obj];
        } else if ([NSStringFromClass([obj class]) isEqualToString:@"__NSCFBoolean"]) {
            [desc appendFormat:@"%@\t%@: %s,\n", tab, key, [(NSNumber *)obj boolValue] ?"true": "false"];
        } else if ([obj isKindOfClass:[NSArray class]]
                   || [obj isKindOfClass:[NSDictionary class]]) {
            [desc appendFormat:@"%@\t%@: %@,\n", tab, key, [obj descriptionWithLocale:locale indent:level + 1]];
        } else if ([obj isKindOfClass:[NSData class]]) {
            // 如果是NSData類型,嘗試去解析結(jié)果,以打印出可閱讀的數(shù)據(jù)
            sz_convertToJsonString(obj, level, desc, tab, key);
        } else if ([obj isKindOfClass:[NSNull class]])  {
            [desc appendFormat:@"%@\t%@: null,\n", tab, key];
        } else {
            [desc appendFormat:@"%@\t%@: %@,\n", tab, key, obj];
        }
    }
    // 查出最后一個,的范圍
    NSRange range = [desc rangeOfString:@"," options:NSBackwardsSearch];
    if (range.length) {
        // 刪掉最后一個,
        [desc deleteCharactersInRange:range];
    }
    [desc appendFormat:@"%@}", tab];
    return desc;
}

- (NSString *)szlog_debugDescription {
    return [self descriptionWithLocale:nil indent:0];
}

+ (void)load {
    SZFUNCTIONSWAPREGISTER//方法交換
}

至于NSArray部分,無非字符串的拼接格式不同而已,就不贅述。

Swift如何使用?

很簡單,拖入工程后,只需將swift中的Dictionary轉(zhuǎn)換成NSDictionary來用即可。舉個??

var dict: [String : Any]  = [
            "key1" : true,
            "key2" : 0.3,
            "key3" : ["key1" : ["1",2,"中文","http://www.baidu.com"],
                      "key2": "value2"],
            
        ]
 print(dict as NSDictionary)

輸出截圖如下:


swift中的字典打印.png

好的,故事寫到這就結(jié)束了,謝謝您的拜讀!音響老師片尾曲請放起來。
慢!請留步,還有彩蛋,哈哈哈。
或許你們有疑惑,開頭講到j(luò)son字符串可以轉(zhuǎn)換成字典,調(diào)用系統(tǒng)現(xiàn)成方法就行。反過來,字典轉(zhuǎn)成json字符串,難道就沒有相應(yīng)的系統(tǒng)方法了?用得著大費(fèi)周章這么解析拼接嗎?的確是有的!我們這就試下

NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted  error:&error];//這里的dict仍是前面用到的字典用例
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(@"系統(tǒng)默認(rèn)打印格式打印jsonString:%@",jsonString);

控制臺輸出


字典轉(zhuǎn)json.png

有個很大的問題:0.3的精度丟失了。此外,網(wǎng)址加上了轉(zhuǎn)義的斜杠“\”。所以這個方案我是不能接受的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容