JSON模型转换与YYModel

作为App端开发,iOS和Android都不可避免要将服务器返回的数据转换为Model以便使用,而服务器返回的数据中又以JSON格式为最为常见。从最初的手动转换到各种JSON模型转换第三方库的出现和使用,我们既节省了大量时间,又提高了工作效率。先来看下常用的有哪几种,Mantle,JSONModel,YYModel等等,我们先来看下三者的使用方法、转换性能和各自的特点。

常用JSON模型转换库评测

JSON模型转换库的核心功能一般有JSON-->Model,Model->JSON,归档等,由于Model-->JSON实际应用情况不多,本次评测只针对了JSON-->Model和归档这两种情况。

常用库测试

begin = CACurrentMediaTime();
for (NSInteger i = 0; i < count; i++) {
    // 模型转换
}
end = CACurrentMediaTime();
NSLog(@"JSON模型转换:%.2f", (end - begin) * 1000);

通过上面代码我们得出下表:

性能评测(毫秒)

模型转换 JSON-->Modle Archive
手动转换 145.94
YYModel 152.08 1396.63
JSONModel 979.96 2006.00
Mantle 2674.07 4737.81

在模型库的对比使用过程中,对于兼容性得出了下表 :

兼容性评测

JSON-->Model YYModel JSONModle Mantle
number-->NSString 正常 正常 nil
string-->NSInteger 正常 nil nil
string-->NSDate 正常 正常 nil
属性名映射 支持 支持 支持(需要全写出)
Archive 支持 支持 支持

通过上面的表格我们大致可以得出下面的结论:

手动转换:

不用引入任何第三方库,性能最高,但是代码量大而繁琐,key一旦写错,这个key对应的属性转换出错。如果类型不符,需要我们手动去判断来容错,否则极易造成崩溃。比如:把number赋给了model的NSString,如果调用NSString的方法,就会造成崩溃。还有如果传回来的是空对象,在转换时会转成NSNull对象,调用时也会造成程序崩溃。

YYModel

作者郭耀源,国内iOS开发者,代表作品YYKit,非常赞的一个开源库,YYModel只是YYKit众多功能组件之一。可以自定义详细的JSON模型转换过程,性能极高,容错率较高。YYModel在转换时回做类型检测,如果类型不符,会对其进行尝试转换(比如string赋给了NSintger),尝试失败时才会将其置为nil。

JSONModel

早期广泛应用的一个JSON模型转换库,在性能上要略劣于YYModel,但是非常好用,使用简单,易于上手。缺点也比较明显,所有的model都需要继承于JSONModel这个基类。对于代码来说,这属于侵入式使用,对于使用者来说有许多不便,灵活性差。比如对瓜子C端的代码,我们有一个model的基类GJECNetworkResponseBaseEntity,我们会在这个model里边来转换一些返回数据的状态信息,比如code和对应的message,一般我们知道返回数据是否正常以及对应的信息。如果我们开发很久了再使用JSONModel,由于它的侵入性会使我们在使用上有很大局限性,代码更改量也是相当的大

Mantle

功能最丰富的一个JSON模型转换库,文档完善,使用也是相当广泛的,也是非常稳定的(转换的时候会做类型检测,类型不服的都会转换成nil,避免将错误的数据类型赋值给对应的属性,以避免潜在的crash问题)。我之前实习的公司中,遨游浏览器的APP开发就是使用的这个作为数据解析库。功能丰富了带来的缺点也是很明显,性能是这几个库中最差的,并且相当明显。转换过程中如果有类型不符的,会终止整个转换过程。Mantle也需要所有的model都要继承于MTLModel,并且实现MTLJSONSerializing协议,也具有侵入性,和JSONModel在灵活性上都是较差的。

总结一下:

JSON模型转换 YYModel JSONModle Mantle
性能 较高 一般(较差)
容错性 一般 较差
功能 简单,方便 简单,方便 丰富,可定制
侵入性 无,灵活性高 有,灵活性差 有,灵活性差
上手难易度 较低 较高

源码分析及项目使用

逻辑流程图

YYModel流程图

核心代码

YYModel虽然只有5个文件,但是逻辑严谨,功能高效,使用方便灵活。核心文件如下:

#import "NSObject+YYModel.h"
#import "NSObject+YYModel.m"
#import "YYClassInfo.h"
#import "YYClassInfo.m"

其中YYClassInfo中包含了YYClassIvarInfo,YYClassMethodInfo,YYClassPropertyInfo这3个内部类,YYClassInfo以字典集合的方式整合了以上三者的信息,达到了进一步封装的目的。

// 获取变量列表
class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) 
// 获取方法列表
class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount) 
// 获取属性列表
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)

通过上面三个方法获得模型类的变量列表,方法列表和属性列表,并把这些信息封装到YYClassInfo和对应的YYClass***Info。并且模型类的这些变量,方法和属性一旦有变化,就要做出相应的更新,提高了缓存信息的准确性。

+ (instancetype)classInfoWithClass:(Class)cls {
    // 从容器中获取缓存信息
    ...
    if (info && info->_needUpdate) {
        [info _update];
    }
    dispatch_semaphore_signal(lock);
    if (!info) {
        info = [[YYClassInfo alloc] initWithClass:cls];
        // 缓存信息到对应容器
        ...
    }
    return info;
}

_update方法中会使用class_copyIvarList,class_copyMethodList和class_copyPropertyList来更新模型类。

在NSObject+YYModel.m中有两个私有类:_YYModelMeta和_YYModelPropertyMeta:

  • _YYModelMeta 用来存储模型类的信息(_classInfo),模型类所有属性对应的映射关系集合和_YYModelPropertyMeta信息等
@interface _YYModelMeta : NSObject {
    @package
    YYClassInfo *_classInfo;
    /// Key:mapped key and key path, Value:_YYModelPropertyMeta.
    NSDictionary *_mapper;
    /// Array<_YYModelPropertyMeta>, all property meta of this model.
    NSArray *_allPropertyMetas;
    /// Array<_YYModelPropertyMeta>, property meta which is mapped to a key path.
    NSArray *_keyPathPropertyMetas;
    /// Array<_YYModelPropertyMeta>, property meta which is mapped to multi keys.
    NSArray *_multiKeysPropertyMetas;
    /// The number of mapped key (and key path), same to _mapper.count.
    NSUInteger _keyMappedCount;
    /// Model class type.
    YYEncodingNSType _nsType;
    
    BOOL _hasCustomWillTransformFromDictionary;
    BOOL _hasCustomTransformFromDictionary;
    BOOL _hasCustomTransformToDictionary;
    BOOL _hasCustomClassFromDictionary;
}
@end
  • _YYModelPropertyMeta 用来存储模型类的property的数据信息,包括属性name,type,class,getter和setter方法等信息。
@interface _YYModelPropertyMeta : NSObject {
    @package
    NSString *_name;             ///< property's name
    YYEncodingType _type;        ///< property's type
    YYEncodingNSType _nsType;    ///< property's Foundation type
    BOOL _isCNumber;             ///< is c number type
    Class _cls;                  ///< property's class, or nil
    Class _genericCls;           ///< container's generic class, or nil if threr's no generic class
    SEL _getter;                 ///< getter, or nil if the instances cannot respond
    SEL _setter;                 ///< setter, or nil if the instances cannot respond
    BOOL _isKVCCompatible;       ///< YES if it can access with key-value coding
    BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver
    BOOL _hasCustomClassFromDictionary; ///< class/generic class implements +modelCustomClassForDictionary:
    
    /*
     property->key:       _mappedToKey:key     _mappedToKeyPath:nil            _mappedToKeyArray:nil
     property->keyPath:   _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
     property->keys:      _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath    _mappedToKeyArray:keys(array)
     */
    NSString *_mappedToKey;      ///< the key mapped to
    NSArray *_mappedToKeyPath;   ///< the key path mapped to (nil if the name is not key path)
    NSArray *_mappedToKeyArray;  ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys)
    YYClassPropertyInfo *_info;  ///< property's info
    _YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key.
}
@end

这两个内部类缓存了模型类的元数据信息,将其放在了一个静态字典中。我们在使用的时候只需要创建一次,其余的就直接使用缓存中的元数据信息就可以,提高了访问和转换效率。

YYModel使用yy_modelWithJSON作为JSON模型转换的入口,将传入的对象转换成字典,真正来实现JSON模型转换功能的是yy_modelSetWithDictionary。这个方法的内部实现如下:

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
    ...
    // 初始化YYModelMeta
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
    
    ...
    // 遍历解析JSON,并完成Model赋值
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        if (modelMeta->_keyPathPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        if (modelMeta->_multiKeysPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } else {
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    ...
    return YES;
}

上面真正起到遍历赋值的方法是CFDictionaryApplyFunctionCFArrayApplyFunction,通过这两个CoreFoundation的方法来遍历传入的字典来给模型类赋值。这两个方法和OC自带的遍历方法相比,会带来不少性能上的提升,缺点就是写起来相当麻烦。这两个方法都有一个回调函数(ModelSetWithDictionaryFunctionModelSetWithPropertyMetaArrayFunction),在遍历字典时,将字典的每一个value赋值给对应的模型类中的对应属性。

YYModel使用比较简单,只需要实现YYModel协议中的一些方法,就可以完成JSON模型转换。

@optional
// Custom property mapper.
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
//  The generic class mapper for container properties.
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
// json-->objec的转换过程中,自定义objec的类别
+ (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
// 黑名单
+ (nullable NSArray<NSString *> *)modelPropertyBlacklist;
// 白名单
+ (nullable NSArray<NSString *> *)modelPropertyWhitelist;
// 在json-->modle之前可以在这个方法中修改json
- (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic;
// 在json-->modle之后可以在这个方法中根据json修改model
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;
// 在json-->modle之后可以在这个方法中根据model修改json
- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic;

参考

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

推荐阅读更多精彩内容

  • 导语:YYModel库是优秀的模型转换库,可自动处理模型转换(从JSON到Model 和 Model到JSON)的...
    南华coder阅读 5,431评论 0 11
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • 【1】 今天要分享的书籍是我主题阅读的第5本《点石成金》(《DON’T MAKE ME THINK》),豆瓣评分有...
    萌萌视觉笔记阅读 335评论 1 2
  • 每天早起的第一件事就是打开有书,聆听主播们的美声音。喜欢朗诵的我,其实,也很喜欢拜读友友们的美文。有时候偷懒没认真...
    靓晶晶68阅读 1,115评论 3 3