[iOS开发必备]一个优雅的打印框架:字典以JSON格式打印

缘起

我们在跟后端对接调试接口的时候,会将返回数据转成字典后打印出来。这时候我们发现,Xcode控制台输出的格式并非JSON格式,跟后端同事沟通协作的时候不是特别方便。特别要吐槽的是打印出来的中文是Unicode编码,这也太反人性了。于是我写了一个框架,完美解决以上痛点。
比如后端下发了这样的JSON数据:

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

实际上,我们是以NSData类型(Objective-C)去接收网络数据的。上述JSON字符串对应的NSData,可通过如下方法转换:

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

此时我们在本地模拟出了接收到的NSData数据,将jsonData转化成字典:

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

debug调试responseObject信息截图如下:


responseObject内存信息.png

直接手写构造上面的字典:

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

Xcode控制台上打印显示:


默认情况下打印字典.png

默认的打印格式除了中文乱码以外,还有两个不容忽视的问题:
1、字典中@"isSingle":@(YES) BOOL类型的值被输出为1
2、字典中@"score":@(0.3) 浮点数0.3被加上了引号变成了字符格式

解决方案

为了解决以上痛点,我查阅了相关资料,写了一个轻量级的零侵入打印框架 SZJsonLog

使用该框架后打印效果是这样的


SZJsonLog打印字典.png

完美还原原始JSON数据。我已经将该框架上传到Github,您可以点击 SZJsonLog源码 下载,文件直接拖入工程,使用系统打印方法即可。祝您享用愉快!

如何实现?

其实很简单,一句话就能说明白:依次取出字典中的键值对,进行字符串拼接。最终输出JSON格式的字符串。
NSLog打印字典(NSDictionary)和数组(NSArray)的时候会走- (NSString *)descriptionWithLocale:(id)locale来决定打印的字符串。所以现在我们在分类中重写NSDictionary和NSArray(两者可以相互嵌套)的- (NSString *)descriptionWithLocale:(id)locale方法来获得我们预期的结果。
在使用po命令调试的时候,会走- (NSString *)debugDescription方法,我们同样覆盖该方法来实现预期效果。
至于零侵入,你们应该想到了,就是利用runtime的方法交换,在编译时注册经过改造的打印方法。
以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类型,尝试去解析结果,以打印出可阅读的数据
            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转换成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

好的,故事写到这就结束了,谢谢您的拜读!音响老师片尾曲请放起来。
慢!请留步,还有彩蛋,哈哈哈。
或许你们有疑惑,开头讲到json字符串可以转换成字典,调用系统现成方法就行。反过来,字典转成json字符串,难道就没有相应的系统方法了?用得着大费周章这么解析拼接吗?的确是有的!我们这就试下

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

控制台输出


字典转json.png

有个很大的问题:0.3的精度丢失了。此外,网址加上了转义的斜杠“\”。所以这个方案我是不能接受的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容