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

参考

  • YYModel

你可能感兴趣的:(JSON模型转换与YYModel)