《YYModel源码分析(二)NSObject+YYModel》

栏目: IOS · 发布时间: 6年前

内容简介:承接上文首先字典转模型,就是字典中key对应的value赋值给model对应的属性的过程,默认情况下我们都会将属性名对应成字典的key,那么如果我们不想这么起名字。或者我们有这样一个json:

承接上文 《YYModel源码分析(一)YYClassInfo》 之前文章讲述了 YYClassInfo 如何将runtime类结构封装到OC层。这篇文章主要讲述YYModel是如何用 NSObject 分类,实现非侵入式json-model的(类型转换,容错,model转json会在其他文章中讨论)。

写在开头

NSObject+ YYModel 中并不只有 NSObject 分类,还包含了 _YYModelPropertyMeta_YYModelMeta 以及协议 <YYModel> ,当然又声明了很多静态(内联)函数,至于为什么用内联函数而不用类方法或者宏定义,是因为内联函数在编译中会将代码插入到调用的位置,这样会提高调用效率,相对于宏又有函数的特点。具体可以看这里《IOS 内联函数Q&A》。

协议

首先字典转模型,就是字典中key对应的value赋值给model对应的属性的过程,默认情况下我们都会将属性名对应成字典的key,那么如果我们不想这么起名字。或者我们有这样一个json:

{
         "n":"Harry Pottery",
         "p": 256,
         "ext" : {
             "desc" : "A book written by J.K.Rowling."
         },
         "ID" : 100010
 }
复制代码

我们想赋值给这个model

@interface YYBook : NSObject
@property NSString *name;
@property NSInteger page;
@property NSString *desc;
@property NSString *bookID;
@end
复制代码

要实现以上的需求就必须告诉YYModel属性应该如何取值, <YYModel> 提供了这样一套规范协议。接下来我们依次看一下

/**
 返回一个map,key是属性名,value是json中对应的key,可以有三种形式。
 
 @{@"name"  : @"n",                         //对应一个json中的key
   @"desc"  : @"ext.desc",                  //对应一个json地址。
   @"bookID": @[@"id", @"ID", @"book_id"]}; //对应多个json中的key。
 */
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
/**
 告诉YYModel容器类型中元素的类型。如下:
 @{@"shadows" : [YYShadow class],
   @"borders" : YYBorder.class,
   @"attachments" : @"YYAttachment" }
 value可以穿Class也可以穿字符串,可以自动解析
 */
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
/**
想根据dictionary提供的数据创建不同的类,实现这个方法,会根据返回的类型创建对象
注意这个协议对`+modelWithJSON:`, `+modelWithDictionary:`,这两个方法有效
 */
+ (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
/**
 在json转model的时候,黑名单上的属性都会被忽略
 */
+ (nullable NSArray<NSString *> *)modelPropertyBlacklist;
/**
 在json转model的时候,如果属性没有在白名单上,将会被忽略。
 */
+ (nullable NSArray<NSString *> *)modelPropertyWhitelist;
/**
 这个方法可以在json转model之前对dic进行更改,json转model将按照返回的dic为准。
 */
- (NSDictionary *)modelCustomWillTransformDictionary:(NSDictionary *)dic;
/**
 该接口会在json转model之后调用,用于不适合模型对象时做额外的逻辑处理。我们也可以用这个接口来验证模型转换的结果
 */
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;
复制代码

静态函数

在NSObject+YYModel.m文件中一看,差不多一半都是静态(内联)函数,内联函数我们前面已经说过了,static修饰函数跟普通函数有以下区别:

  • 语法与C++保持一致,只在模块内部可见
  • 跟类无关,所以也无法调用self,只能根据参数实现相关功能
  • 静态参数不参与动态派发,没有再函数列表里,静态绑定 所以因为要频繁调用,所以寻求更高效的static函数。我把静态函数和其功能都列在下面了,供参考。
//将类解析成Foundation类型,传入Class返回枚举YYEncodingNSType
static force_inline YYEncodingNSType YYClassGetNSType(Class cls) 
//通过YYEncodingType判断是否是c数字类型
static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type)
//将一个ID类型的数据解析成NSNumber,这里主要处理了字符串转数字的情况
static force_inline NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value)
//NSString类型数据转NSDate,这里几乎兼容了所有时间格式,并且做了容错
static force_inline NSDate *YYNSDateFromString(__unsafe_unretained NSString *string)
//获取NSBlock这个类,加入了打印我们可以看出 block 的父类的关系是block -------> NSGlobalBlock ---------> NSBlock
static force_inline Class YYNSBlockClass() 
//获取ISO时间格式
static force_inline NSDateFormatter *YYISODateFormatter()
//根据KeyPath获取一个字典中的数据
static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) 
//一句多个Key从字典中获取数据,这里如果有一个Key有值就取值返回。
static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) 
//
static force_inline NSNumber *ModelCreateNumberFromProperty(__unsafe_unretained id model,
                                                            __unsafe_unretained _YYModelPropertyMeta *meta)
//为一个对象设置数值属性
static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model,
                                                  __unsafe_unretained NSNumber *num,
                                                  __unsafe_unretained _YYModelPropertyMeta *meta)
//为对象的属性赋值
static void ModelSetValueForProperty(__unsafe_unretained id model,
                                     __unsafe_unretained id value,
                                     __unsafe_unretained _YYModelPropertyMeta *meta)
//通过键值为_context设置属性,_context是一个结构体,后面我们会讲到,包含了数据源dic、model和_YYModelMeta。
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context)
//为对象的_propertyMeta属性赋值。
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) 
//由model返回一个有效的json。
static id ModelToJSONObjectRecursive(NSObject *model) 
复制代码

关于这些方法的实现,后面用到会细说。

_YYModelPropertyMeta

其实 _YYModelPropertyMeta 类型是在 YYClassPropertyInfo 的基础上的进一步解析并且关联了从 <YYModel> 协议中的取值信息。

/// A property info in object model.
@interface _YYModelPropertyMeta : NSObject {
    @package
    NSString *_name;             ///< 属性名
    YYEncodingType _type;        ///< 属性类型,OC类型统一为YYEncodingTypeObject
    YYEncodingNSType _nsType;    ///< 属性的Foundation类型,NSString等等。
    BOOL _isCNumber;             ///< 是否是c数字类型
    Class _cls;                  ///< 属性类型,
    Class _genericCls;           ///< 如果是容器类型,是容器类型内元素的类型,如果不是容器类型为nil。
    SEL _getter;                 ///< getter方法
    SEL _setter;                 ///< setter方法
    BOOL _isKVCCompatible;       ///< 是否可以使用KVC
    BOOL _isStructAvailableForKeyedArchiver; ///< 结构体是否支持归档解挡
    BOOL _hasCustomClassFromDictionary; ///< 是否实现了 +modelCustomClassForDictionary:协议
    
    NSString *_mappedToKey;      ///< 表明该属性取数据源中_mappedToKey对应的value的值。
    NSArray *_mappedToKeyPath;   ///< 表明该属性取数据源中_mappedToKeyPath对应路径的value值,如果为nil说明没有关键路径
    NSArray *_mappedToKeyArray;  ///< key或者keyPath的数组,表明可从多个key中取值。
    YYClassPropertyInfo *_info;  ///< 属性信息
    _YYModelPropertyMeta *_next; ///< 下一个元数据,如果有多个属性映射到同一个键。
}
@end
复制代码

_YYModelPropertyMeta 属性我们可以看出,如果属性是Foundation类型,会被解析成具体的OC类型,用枚举的形式存储在 _nstype 中,同时由Model实现的 <YYModel> 协议可以获取到取值信息 _mappedToKey_mappedToKeyPath _mappedToKeyArray 信息,这个在之后的赋值操作中起着至关重要的作用。

@implementation _YYModelPropertyMeta

+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
    // 这里有些许疑惑,generic是当属性是容器类时,容器类中包含的元素,代码逻辑是如果generic为空,且propertyInfo.protocols不为空,如果propertyInfo.protocols中的元素是Class的时候将此class赋值给generic,但是propertyInfo.protocols确实存储的是协议,propertyInfo.protocols的解析过程是取objc_property_attribute_t中<>中的字符,但是经测试只有一个属性遵循了某种协议才会出现<>字符,NSSArray<NSString*> *这样的属性编码字符串也是@"NSSArray",所以这块貌似没什么用。
    if (!generic && propertyInfo.protocols) {
        //
        for (NSString *protocol in propertyInfo.protocols) {
            Class cls = objc_getClass(protocol.UTF8String);
            if (cls) {
                generic = cls;
                break;
            }
        }
    }
    
    _YYModelPropertyMeta *meta = [self new];
    //给meta的成员变量赋值
    meta->_name = propertyInfo.name;
    //类型枚举
    meta->_type = propertyInfo.type;
    //存储属性元数据
    meta->_info = propertyInfo;
    //容器类包含的通用类型
    meta->_genericCls = generic;
    //如果属性是OC类型的
    if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) {
        //解析成枚举
        meta->_nsType = YYClassGetNSType(propertyInfo.cls);
    } else {
        //判断是否是number类
        meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type);
    }
    //如果是结构图
    if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) {
        static NSSet *types = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSMutableSet *set = [NSMutableSet new];
            // 32 bit
            [set addObject:@"{CGSize=ff}"];
            [set addObject:@"{CGPoint=ff}"];
            [set addObject:@"{CGRect={CGPoint=ff}{CGSize=ff}}"];
            [set addObject:@"{CGAffineTransform=ffffff}"];
            [set addObject:@"{UIEdgeInsets=ffff}"];
            [set addObject:@"{UIOffset=ff}"];
            // 64 bit
            [set addObject:@"{CGSize=dd}"];
            [set addObject:@"{CGPoint=dd}"];
            [set addObject:@"{CGRect={CGPoint=dd}{CGSize=dd}}"];
            [set addObject:@"{CGAffineTransform=dddddd}"];
            [set addObject:@"{UIEdgeInsets=dddd}"];
            [set addObject:@"{UIOffset=dd}"];
            types = set;
        });
        //如果是以上结构体则支持归解档
        if ([types containsObject:propertyInfo.typeEncoding]) {
            meta->_isStructAvailableForKeyedArchiver = YES;
        }
    }
    meta->_cls = propertyInfo.cls;
    
    if (generic) {
        //容器类元素是否实现了 modelCustomClassForDictionary协议
        meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)];
    } else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) {
        meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];
    }
    
    //设置getter方法
    if (propertyInfo.getter) {
        if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
            meta->_getter = propertyInfo.getter;
        }
    }
    //设置setter方法
    if (propertyInfo.setter) {
        if ([classInfo.cls instancesRespondToSelector:propertyInfo.setter]) {
            meta->_setter = propertyInfo.setter;
        }
    }
    
    if (meta->_getter && meta->_setter) {
        /*
         以下类型都不支持KVC
         */
        switch (meta->_type & YYEncodingTypeMask) {
            case YYEncodingTypeBool:
            case YYEncodingTypeInt8:
            case YYEncodingTypeUInt8:
            case YYEncodingTypeInt16:
            case YYEncodingTypeUInt16:
            case YYEncodingTypeInt32:
            case YYEncodingTypeUInt32:
            case YYEncodingTypeInt64:
            case YYEncodingTypeUInt64:
            case YYEncodingTypeFloat:
            case YYEncodingTypeDouble:
            case YYEncodingTypeObject:
            case YYEncodingTypeClass:
            case YYEncodingTypeBlock:
            case YYEncodingTypeStruct:
            case YYEncodingTypeUnion: {
                meta->_isKVCCompatible = YES;
            } break;
            default: break;
        }
    }
    
    return meta;
}
@end
复制代码

_YYModelMeta

_YYModelMeta 通过Model遵循的 <YYModel> 协议,收集取值信息,并映射到 _YYModelPropertyMeta 当中,将其中有效的信息封装到该类中。

@interface _YYModelMeta : NSObject {
    //@package当前framework可以使用,外部不可以
    @package
    
    YYClassInfo *_classInfo;
    /// [key:_YYModelPropertyMeta]
    NSDictionary *_mapper;
    /// 所有的属性_YYModelPropertyMeta数据,这里包含当前类到跟类NSObject中的所有属性
    NSArray *_allPropertyMetas;
    /// 映射到KeyPath的属性_keyPathPropertyMetas集合
    NSArray *_keyPathPropertyMetas;
    /// 映射到多个键值的属性_keyPathPropertyMetas集合
    NSArray *_multiKeysPropertyMetas;
    /// 属性映射的数量。
    NSUInteger _keyMappedCount;
    /// Foundation类型
    YYEncodingNSType _nsType;
    
    BOOL _hasCustomWillTransformFromDictionary;
    BOOL _hasCustomTransformFromDictionary;
    BOOL _hasCustomTransformToDictionary;
    BOOL _hasCustomClassFromDictionary;
}
@end
复制代码

接下来讨论一下 _YYModelMet 是如何初始化的。过程如下

  • 1.从实现的 modelPropertyBlacklist、modelPropertyWhitelist 协议中获取取值黑名单、白名单。
  • 2.从实现的 modelContainerPropertyGenericClass 协议中获取容器类属性中的元素类型
  • 3.获取当前类及继承链直至 NSObject 中所有的属性生成 _YYModelPropertyMeta 对象,存储到 allPropertyMetas
  • 4.从实现的 modelCustomPropertyMapper 协议中获取自定义map,这里map的key是属性名,value有三种情况,第一是对应一个取值key,第二是一个keypath用'.'隔开,第三是一个字符数组对应多个取值key
  • 5.遍历map,由mapkey取出对应的 propertyMeta 然后根据步骤4中value的三种情况给 propertyMeta_mappedToKey、_mappedToKeyPath、_mappedToKeyArray 赋值,这样就把属性和取值逻辑绑定在了一起
  • 6.给_keyMappedCount赋值,查看 modelCustomWillTransformFromDictionary、modelCustomTransformFromDictionary、modelCustomTransformToDictionary 、modelCustomClassForDictionary 这四个协议是否实现。

这个过程代码比较多,就不列出来了。感兴趣的可以自己看下哈。

NSObject (YYModel)

NSObject (YYModel) 是YYModel非侵入式的关键,模型对象通过调用扩展方法实现json转model。接下来我们用json-model的核心方法 yy_modelWithDictionary 举例。

+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
    //容错处理
    if (!dictionary || dictionary == (id)kCFNull) return nil;
    if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
    //获取当前类的类型
    Class cls = [self class];
    //创建_YYModelMeta
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
    //这里创建_YYModelMeta的目的就是查看是否实现了modelCustomClassForDictionary协议,哈哈,这里回溯一下modelCustomClassForDictionary的功能,这个协议你可以根据dictionary数据创建一个不同于当前类的对象来完成json转model。
    if (modelMeta->_hasCustomClassFromDictionary) {
        //如果实现了这个协议则替换当前类型。
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    //由获取到的类型创建对象
    NSObject *one = [cls new];
    //调用yy_modelSetWithDictionary方法。
    if ([one yy_modelSetWithDictionary:dictionary]) return one;
    return nil;
}
复制代码

再看一下属性赋值的方法 yy_modelSetWithDictionary

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
    //容错处理
    if (!dic || dic == (id)kCFNull) return NO;
    if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    //创建_YYModelMeta
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
    if (modelMeta->_keyMappedCount == 0) return NO;
    //查看是否实现modelCustomWillTransformFromDictionary协议,如果实现调用该方法,处理dic
    if (modelMeta->_hasCustomWillTransformFromDictionary) {
        dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    }
    //创建ModelSetContext,一个结构体
    //    typedef struct {
    //        void *modelMeta;  ///< _YYModelMeta
    //        void *model;      ///< id (self)
    //        void *dictionary; ///< NSDictionary (json)
    //    } ModelSetContext;
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);
        //如果自定义的键值数量大于等于数据源的键值数量,那么按照自定义键值处理
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        //CFDictionaryApplyFunction意思是为字典中的每个键值对调用一次函数
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        if (modelMeta->_keyPathPropertyMetas) {
            //处理取值为_keyPathPropertyMetas形式的属性
            //CFArrayApplyFunction是为数组中的每个元素对调用一次函数。
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        if (modelMeta->_multiKeysPropertyMetas) {
            //处理取值为_multiKeysPropertyMetas形式的属性
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } else {
        //如果自定义键值数量小于数据源的键值数量,那么直接按照dic key值给属性赋值,自定义的无效
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}
复制代码

通过以上代码逻辑我们知道,如果没有设置全量键值映射,也就是说实际数据源的键值数量大于自定义键值数量,那么自定义键值无效,会直接按照实际数据源的key对应属性名进行赋值。

我们可以看到赋值操作中有两个比较重要的方法 ModelSetWithDictionaryFunction,ModelSetWithPropertyMetaArrayFunction

/**
 通过键值给模型赋值
 
 @param _key     键
 @param _value   值
 @param _context 赋值必要的数据,model,modelMeta,dictionary
 */
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
    ModelSetContext *context = _context;
    __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
    //通过key取到响应的属性
    __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
    __unsafe_unretained id model = (__bridge id)(context->model);
    while (propertyMeta) {
        if (propertyMeta->_setter) {
            ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    };
}
/**
 为模型的某一个属性赋值
 
 @param _propertyMeta 属性
 @param _context   赋值必要的数据,model,modelMeta,dictionary
 */
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
    ModelSetContext *context = _context;
    __unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
    __unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);
    if (!propertyMeta->_setter) return;
    id value = nil;
    
    if (propertyMeta->_mappedToKeyArray) {
        value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
    } else if (propertyMeta->_mappedToKeyPath) {
        value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
    } else {
        value = [dictionary objectForKey:propertyMeta->_mappedToKey];
    }
    
    if (value) {
        __unsafe_unretained id model = (__bridge id)(context->model);
        ModelSetValueForProperty(model, value, propertyMeta);
    }
}
复制代码

可以看到这两个方法同归,在取到值之后都调用了 ModelSetValueForProperty 的方法,这个才是真正属性赋值的方法。这个函数做的就是通过runtime函数 objc_msgSend 调用对象的setter方法赋值,之所以代码量巨大是因为对所有的数据类型(c数字,foundation类型)做了判断并添加了大量的容错。关于类型转换和容错之后会单独出一篇文章谈论。

总结

  • YYModel通过扩展实现了无侵入式操作
  • 协议使Model与YYModel进行数据交互
  • YYClassInfo封装Model类型的runtime数据
  • _YYModelPropertyMeta将属性与取值信息绑定
  • _YYModelMeta封装所有的_YYModelPropertyMeta属性
  • 最后通过runtime接口调用属性对应的setter方法赋值

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

An Introduction to Genetic Algorithms

An Introduction to Genetic Algorithms

Melanie Mitchell / MIT Press / 1998-2-6 / USD 45.00

Genetic algorithms have been used in science and engineering as adaptive algorithms for solving practical problems and as computational models of natural evolutionary systems. This brief, accessible i......一起来看看 《An Introduction to Genetic Algorithms》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

html转js在线工具
html转js在线工具

html转js在线工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具