您现在的位置是:首页 > 文章详情

阅读YYModel

日期:2018-09-18点击:446

YYModel库中涉及到Runtime、CF API、信号和锁、位的操作。学习该库可以学习到使用Runtime获取类的信息,包括:类属性信息、类ivar信息、类方法、类型编码;使用runtime底层技术进行方法调用,也就是objc_msgSend方法的使用;dispatch_semaphore_t信号锁的使用;CF框架中CFMutableDictionaryRef/CFMutableDictionaryRef对象的操作;位的操作。

  • 简单使用介绍
    • 简单的转化
    • 自定义属性和json字段的映射配置
    • 黑名单和白名单配置
    • 类型映射配置
  • 预备知识
    • Type Encodings
    • Property Type String
  • 代码解析
    • 类信息的获取
    • 配置信息的获取
    • 模型对象的转换
  • 其它重要知识点
    • 位操作
    • 信号和锁
    • CF框架API

YYModel使用介绍

首先会介绍下YYModel的使用,作为下面代码解析章节的铺垫,代码解析章节以使用方式作为入口点切入,研究框架整体的实现思路、步骤以及每个步骤使用的详细技术。

简单的转化

类属性的定义和json数据中的key是一致的,这种情况最为简单,不用配置映射关系,使用NSObject+YYModel的方法yy_modelWithDictionary获取yy_modelWithJSON即可以把数据反序列化为对象。

比如元素的JSON数据如下所示:

 { "id": 7975492, "title": "同学们,开学了,准备好早起了吗?\n", "source_text": "来自新浪微博", "share_count": 1, "praise_num": 999, "comment_num": 0, "is_praise": 0 } 

定义的数据模型类如下:

@interface IMYOriginalRecommendWeiboModel : IMYRecommendBaseModel <YYModel> @property (nonatomic, copy) NSString *source_text; @property (nonatomic, assign) NSInteger share_count; @property (nonatomic, assign) NSInteger praise_num; ///< 点赞数 @property (nonatomic, assign) BOOL is_praise; ///< 是否已点赞 @property (nonatomic, assign) NSInteger comment_num; ///< 评论数量 @end 

转换后的结果如下:

简单的转化-转换后的结果1

自定义属性和json字段的映射配置

多数情况下,服务端的接口是为了多平台开发的,不同平台的属性定义标准、格式不一样,多数情况下我们需要把服务端的数据个数映射为平台适应的格式,这种情况需要用到配置自定义属性和json字段的映射,可以重写YYModel协议的方法modelCustomPropertyMapper返回一个配置。

定义的数据模型类如下:

@interface IMYOriginalRecommendWeiboModel : IMYRecommendBaseModel <YYModel> @property (nonatomic, copy) NSString *sourceText; @property (nonatomic, assign) NSInteger shareCount; @property (nonatomic, assign) NSInteger praiseCount; ///< 点赞数 @property (nonatomic, assign) BOOL isPraise; ///< 是否已点赞 @property (nonatomic, assign) NSInteger commentCount; ///< 评论数量 @end 

映射关系配置如下:

@implementation IMYRecommendWeiboModel + (NSDictionary *)modelCustomPropertyMapper { return @{@"sourceText" : @"source_text", @"shareCount" : @"share_count", @"praiseCount" : @"praise_num", @"isPraise" : @"is_praise", @"commentCount" : @"comment_num", }; } @end 

转换后的结果如下:

自定义属性和json字段的映射配置-转换后的结果2

黑名单和白名单配置

黑名单配置如下:

@implementation IMYRecommendBlackListWeiboModel + (NSDictionary *)modelCustomPropertyMapper { return @{@"sourceText" : @"source_text", @"shareCount" : @"share_count", @"praiseCount" : @"praise_num", @"isPraise" : @"is_praise", @"commentCount" : @"comment_num", }; } + (NSArray<NSString *> *)modelPropertyBlacklist { return @[@"sourceText", @"shareCount"]; } @end 

反序列化的结果,配置在黑名单中的属性(sourceTextshareCount)不会被赋值

黑名单配置-转换后的结果3

白名单配置如下:

@implementation IMYRecommendWhiteListWeiboModel + (NSDictionary *)modelCustomPropertyMapper { return @{@"sourceText" : @"source_text", @"shareCount" : @"share_count", @"praiseCount" : @"praise_num", @"isPraise" : @"is_praise", @"commentCount" : @"comment_num", }; } + (NSArray<NSString *> *)modelPropertyWhitelist { return @[@"sourceText", @"shareCount"]; } @end 

反序列化的结果,只有配置在白名单中的属性(sourceTextshareCount)才会被赋值

白名单配置-转换后的结果4

类型映射配置

类型映射配置用于属性是数组类型,需要配置该属性中元素的类类型,比如我们定义的数据模型如下,有个users指定是数组类型

@interface IMYRecommendExtendWeiboModel : IMYRecommendBaseModel <YYModel> @property (nonatomic, copy) NSString *sourceText; @property (nonatomic, assign) NSInteger shareCount; @property (nonatomic, assign) NSInteger praiseCount; ///< 点赞数 @property (nonatomic, assign) BOOL isPraise; ///< 是否已点赞 @property (nonatomic, assign) NSInteger commentCount; ///< 评论数量 @property (nonatomic, strong) NSArray<IMYUser *> *users; @end 

类型映射配置如下:

@implementation IMYRecommendBlackListWeiboModel + (NSDictionary *)modelCustomPropertyMapper { return @{@"sourceText" : @"source_text", @"shareCount" : @"share_count", @"praiseCount" : @"praise_num", @"isPraise" : @"is_praise", @"commentCount" : @"comment_num", }; } + (NSDictionary *)modelContainerPropertyGenericClass { return @{@"users" : [IMYUser class]}; } @end 

反序列化的结果如下:
类型映射配置-转换后的结果5

预备知识

首先需要了解的是苹果对于类型说明的的一些规范,包括了Type EncodingsProperty Type String,YYModel代码中重要的一部分就是对这些信息进行建模处理。

Type Encodings

Type Encodings 指的是属性、参数、变量的类型,比如基本数据类型有char、int、short、long、等;此外还有对象类型、类类型、数组类型、结构体类型、共用体类型、OC中的SEL类型等。其中Block 类型的比较特殊,该类型的的类型定义为:"@?",在YYModel中处理Block类型的代码如下:

case '@': { if (len == 2 && *(type + 1) == '?') return YYEncodingTypeBlock | qualifier; else return YYEncodingTypeObject | qualifier; } 

Property Type String

Property Type String 指的是property的内存属性、读写属性、原子属性、getter/getter属性、属性对应的ivar名称以及属性本身的Type Encoding,其中property的内存属性、读写属性、原子属性是只有属性名称,没有属性值,而property的getter/getter属性、属性对应的ivar名称以及属性本身的Type Encoding除了属性名称之外还会附加一个属性的值。查看property的属性名字和值可以使用property_getAttributes方法获取,返回的是一个const char *类型,如果需要对属性的名称和值进行详细的分析可以使用property_copyAttributeList获取到一个数组,数组的元素是objc_property_attribute_t这种结构体类型的,已经解析好了namevalue,方便使用。

假设定义有如下的属性@property(getter=isIntReadOnlyGetter, readonly) int intReadonlyGetter; ,使用property_getAttributes方法获取到的属性对应的属性描述如下:Ti,R,GisIntReadOnlyGetterintReadonlyGetter属性对应的objc_property_attribute_t类型为p,基本的规则解释如下:

  • 类型 p.name == 'T',需要获取 p.value值,然后查询Type Encodings表格
  • 对应的ivar名称 p.name == 'V',需要获取 p.value值,p.value值为ivar名称
  • 读写属性 p.name == 'R' -> readonly
  • 内存属性 p.name == 'C' -> copy; p.name == '&' -> retain; p.name == 'W' -> weak
  • 原子属性 p.name == 'N' -> nonatomic
  • getter/setter属性 p.name = 'G<name>' -> G后面的内容为自定义的getter方法名称,使用p.value获取;p.name = 'S<name>' -> S后面的内容为自定义的setter方法名称,使用p.value获取

以下是一段使用property_getAttributes方法获取属性名字和值得示例代码

定义一个测试的类包含有如下几个属性

@interface MyObj : NSObject @property (nonatomic, copy) MyBlock block; @property struct YorkshireTeaStruct structDefault; @property(nonatomic, readonly, retain) id idReadonlyRetainNonatomic; @property(getter=isIntReadOnlyGetter, readonly) int intReadonlyGetter; @end 

使用如下的代码解析property的属性

 char *propertyAttributes = (char *)property_getAttributes(property); NSString *propertyAttributesString = [NSString stringWithUTF8String:propertyAttributes]; NSLog(@"propertyAttributesString = %@", propertyAttributesString); 

使用以上的代码对MyObj类的属性进行分析,打印的内容如下,可以对照Property Type StringType Encodings查看详细的说明:

propertyAttributesString = T@?,C,N,V_block propertyAttributesString = T{YorkshireTeaStruct=ic},V_structDefault propertyAttributesString = T@,R,&,N,V_idReadonlyRetainNonatomic propertyAttributesString = Ti,R,GisIntReadOnlyGetter,V_intReadonlyGetter 

代码解析

类信息的获取

类信息的获取主要是在YYClassInfo类的方法_update中处理的,包括类的类对象、父类的类对象、父类的类信息、是否是元类、属性信息、方法信息、ivar信息。实际上方法信息ivar信息这两个信息并没有真正的使用到,但是框架中有进行处理,为了完整性还是稍作叙述。主要使用到的还是propertyInfos属性中的内容,后面的配置信息获取和模型对象转换都需要使用到。

YYClassInfo类的定义主要如下

@interface YYClassInfo : NSObject @property (nonatomic, assign, readonly) Class cls; ///< class object @property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object @property (nullable, nonatomic, assign, readonly) Class metaCls; ///< class's meta class object @property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class @property (nonatomic, strong, readonly) NSString *name; ///< class name @property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info @property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars @property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods @property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties // ... 

流程时序图如下所示:

类信息的获取流程时序图

获取类信息的操作主要都在_update方法中,主要步骤如下

  • class_copyMethodList 方法获取类中的方法信息,方法信息转换为YYClassMethodInfo对象,保存在methodInfos属性中
  • class_copyPropertyList 方法获取类中的属性信息,属性信息转换为YYClassPropertyInfo对象,保存在propertyInfos属性中
  • class_copyIvarList 方法获取类中的ivar信息,ivar信息转换为YYClassIvarInfo对象,保存在ivarInfos属性中

这里需要注意的是class_copyMethodListclass_copyPropertyListclass_copyIvarList涉及到内存的拷贝问题,使用完成之后需要使用C的方法free释放拷贝的内容,防止内存泄漏。上面有提到说方法信息ivar信息这两个信息并没有真正的使用到,接下来会着重的介绍属性信息的获取中的一些细节。

- (void)_update { _ivarInfos = nil; _methodInfos = nil; _propertyInfos = nil; Class cls = self.cls; // 获取类中的方法信息 unsigned int methodCount = 0; Method *methods = class_copyMethodList(cls, &methodCount); if (methods) { NSMutableDictionary *methodInfos = [NSMutableDictionary new]; _methodInfos = methodInfos; for (unsigned int i = 0; i < methodCount; i++) { YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]]; if (info.name) methodInfos[info.name] = info; } free(methods); } // 获取类中的属性信息 unsigned int propertyCount = 0; objc_property_t *properties = class_copyPropertyList(cls, &propertyCount); if (properties) { NSMutableDictionary *propertyInfos = [NSMutableDictionary new]; _propertyInfos = propertyInfos; for (unsigned int i = 0; i < propertyCount; i++) { YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]]; if (info.name) propertyInfos[info.name] = info; } free(properties); } // 获取类中的ivar信息 unsigned int ivarCount = 0; Ivar *ivars = class_copyIvarList(cls, &ivarCount); if (ivars) { NSMutableDictionary *ivarInfos = [NSMutableDictionary new]; _ivarInfos = ivarInfos; for (unsigned int i = 0; i < ivarCount; i++) { YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]]; if (info.name) ivarInfos[info.name] = info; } free(ivars); } if (!_ivarInfos) _ivarInfos = @{}; if (!_methodInfos) _methodInfos = @{}; if (!_propertyInfos) _propertyInfos = @{}; _needUpdate = NO; } 

属性信息获取

属性信息主要包含属性名字、属性的Encoding 类型、属性所属的类、属性的getter/setter方法的选择子SEL。这些信息会保存在属性信息类YYClassPropertyInfo中,属性信息类YYClassPropertyInfo定义如下:

@interface YYClassPropertyInfo : NSObject @property (nonatomic, assign, readonly) objc_property_t property; ///< property's opaque struct @property (nonatomic, strong, readonly) NSString *name; ///< property's name @property (nonatomic, assign, readonly) YYEncodingType type; ///< property's type @property (nonatomic, strong, readonly) NSString *typeEncoding; ///< property's encoding value @property (nonatomic, strong, readonly) NSString *ivarName; ///< property's ivar name @property (nullable, nonatomic, assign, readonly) Class cls; ///< may be nil @property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; ///< may nil @property (nonatomic, assign, readonly) SEL getter; ///< getter (nonnull) @property (nonatomic, assign, readonly) SEL setter; ///< setter (nonnull) // ... 

属性处理主要在initWithProperty方法中进行,这部分的内容在前面的预备知识中的Type EncodingsProperty Type String有讲到了大部分,主要步骤如下:

  • 获取property名字
  • 读取property的属性名字和属性值,建立属性的Encoding模型
  • 处理getter/setter
- (instancetype)initWithProperty:(objc_property_t)property { if (!property) return nil; self = [super init]; _property = property; // 获取property名字 const char *name = property_getName(property); if (name) { _name = [NSString stringWithUTF8String:name]; } // 读取property的属性名字和属性值,建立属性的Encoding模型 YYEncodingType type = 0; unsigned int attrCount; objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount); for (unsigned int i = 0; i < attrCount; i++) { switch (attrs[i].name[0]) { case 'T': { // Type encoding if (attrs[i].value) { _typeEncoding = [NSString stringWithUTF8String:attrs[i].value]; type = YYEncodingGetType(attrs[i].value); if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) { NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding]; if (![scanner scanString:@"@\"" intoString:NULL]) continue; NSString *clsName = nil; if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) { if (clsName.length) _cls = objc_getClass(clsName.UTF8String); } NSMutableArray *protocols = nil; while ([scanner scanString:@"<" intoString:NULL]) { NSString* protocol = nil; if ([scanner scanUpToString:@">" intoString: &protocol]) { if (protocol.length) { if (!protocols) protocols = [NSMutableArray new]; [protocols addObject:protocol]; } } [scanner scanString:@">" intoString:NULL]; } _protocols = protocols; } } } break; case 'V': { // Instance variable if (attrs[i].value) { _ivarName = [NSString stringWithUTF8String:attrs[i].value]; } } break; case 'R': { type |= YYEncodingTypePropertyReadonly; } break; case 'C': { type |= YYEncodingTypePropertyCopy; } break; case '&': { type |= YYEncodingTypePropertyRetain; } break; case 'N': { type |= YYEncodingTypePropertyNonatomic; } break; case 'D': { type |= YYEncodingTypePropertyDynamic; } break; case 'W': { type |= YYEncodingTypePropertyWeak; } break; case 'G': { type |= YYEncodingTypePropertyCustomGetter; if (attrs[i].value) { _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]); } } break; case 'S': { type |= YYEncodingTypePropertyCustomSetter; if (attrs[i].value) { _setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]); } } // break; commented for code coverage in next line default: break; } } if (attrs) { free(attrs); attrs = NULL; } _type = type; // 处理getter、setter方法 if (_name.length) { if (!_getter) { _getter = NSSelectorFromString(_name); } if (!_setter) { _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]); } } return self; } 

配置信息的获取

配置信息的获取主要流程时序图如下:
配置信息的获取主要流程时序图

主要有以下几个步骤:

  • 黑名单配置获取
  • 白名单配置获取
  • 容器类型中元素类型配置获取
  • 递归遍历当前类和父类的propertyInfos属性,获取所有property的元数据
  • 处理自定义的属性对于json数据key的映射配置

对应的代码如下,关键的地方有添加了注释:

- (instancetype)initWithClass:(Class)cls { YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls]; if (!classInfo) return nil; self = [super init]; // Get black list // 黑名单配置获取 NSSet *blacklist = nil; if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) { NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist]; if (properties) { blacklist = [NSSet setWithArray:properties]; } } // Get white list // 白名单配置获取 NSSet *whitelist = nil; if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) { NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist]; if (properties) { whitelist = [NSSet setWithArray:properties]; } } // Get container property's generic class // 容器类型中元素类型配置获取 NSDictionary *genericMapper = nil; if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) { genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass]; if (genericMapper) { NSMutableDictionary *tmp = [NSMutableDictionary new]; [genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { if (![key isKindOfClass:[NSString class]]) return; Class meta = object_getClass(obj); if (!meta) return; if (class_isMetaClass(meta)) { tmp[key] = obj; } else if ([obj isKindOfClass:[NSString class]]) { Class cls = NSClassFromString(obj); if (cls) { tmp[key] = cls; } } }]; genericMapper = tmp; } } // 递归遍历当前类和父类的propertyInfos属性,获取所有property的元数据 // property的元数据对应的私有类为_YYModelPropertyMeta,其实就是YYClassPropertyInfo类对象的封装 NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new]; YYClassInfo *curClassInfo = classInfo; while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy) for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) { if (!propertyInfo.name) continue; if (blacklist && [blacklist containsObject:propertyInfo.name]) continue; if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue; _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo propertyInfo:propertyInfo generic:genericMapper[propertyInfo.name]]; if (!meta || !meta->_name) continue; if (!meta->_getter || !meta->_setter) continue; if (allPropertyMetas[meta->_name]) continue; allPropertyMetas[meta->_name] = meta; } curClassInfo = curClassInfo.superClassInfo; } if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy; // create mapper // 处理自定义的属性对于json数据key的映射配置 NSMutableDictionary *mapper = [NSMutableDictionary new]; NSMutableArray *keyPathPropertyMetas = [NSMutableArray new]; NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new]; if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) { NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper]; [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) { _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName]; if (!propertyMeta) return; [allPropertyMetas removeObjectForKey:propertyName]; if ([mappedToKey isKindOfClass:[NSString class]]) { if (mappedToKey.length == 0) return; propertyMeta->_mappedToKey = mappedToKey; NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."]; for (NSString *onePath in keyPath) { if (onePath.length == 0) { NSMutableArray *tmp = keyPath.mutableCopy; [tmp removeObject:@""]; keyPath = tmp; break; } } if (keyPath.count > 1) { propertyMeta->_mappedToKeyPath = keyPath; [keyPathPropertyMetas addObject:propertyMeta]; } // 处理有多个属性绑定到同一个json数据key的配置 propertyMeta->_next = mapper[mappedToKey] ?: nil; mapper[mappedToKey] = propertyMeta; } else if ([mappedToKey isKindOfClass:[NSArray class]]) { NSMutableArray *mappedToKeyArray = [NSMutableArray new]; for (NSString *oneKey in ((NSArray *)mappedToKey)) { if (![oneKey isKindOfClass:[NSString class]]) continue; if (oneKey.length == 0) continue; NSArray *keyPath = [oneKey componentsSeparatedByString:@"."]; if (keyPath.count > 1) { [mappedToKeyArray addObject:keyPath]; } else { [mappedToKeyArray addObject:oneKey]; } if (!propertyMeta->_mappedToKey) { propertyMeta->_mappedToKey = oneKey; propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil; } } if (!propertyMeta->_mappedToKey) return; propertyMeta->_mappedToKeyArray = mappedToKeyArray; [multiKeysPropertyMetas addObject:propertyMeta]; // 处理有多个属性绑定到同一个json数据key的配置 propertyMeta->_next = mapper[mappedToKey] ?: nil; mapper[mappedToKey] = propertyMeta; } }]; } // 处理默认的属性的映射配置,属性在json数据中的key就是属性的原始名称 [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) { propertyMeta->_mappedToKey = name; propertyMeta->_next = mapper[name] ?: nil; mapper[name] = propertyMeta; }]; // 最终把json数据key和属性的映射关系保存起来 if (mapper.count) _mapper = mapper; if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas; if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas; _classInfo = classInfo; _keyMappedCount = _allPropertyMetas.count; _nsType = YYClassGetNSType(cls); _hasCustomWillTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)]); _hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]); _hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]); _hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]); return self; } 

模型对象的转换

模型对象的转换的步骤时序图如下:

模型对象的转换的步骤时序图

入口函数yy_modelSetWithDictionary的功能如下:

  • 1、属性的映射配置的个数大于json数据元素的个数(if分支),优先处理json数据
    • 1.1、遍历json数据的key
    • 1.2、根据key从meta->_mapper配置中寻找映射的_YYModelPropertyMeta对象
    • 1.3、从json数据中读取值,给属性设置值。
  • 2、json数据元素的个数大于属性的映射配置的个数(else分支),优先处理属性属性
    • 2.1、从modelMeta->_allPropertyMetas读取属性配置
    • 2.2、从json数据中读取值,给属性设置值

对应的代码如下,关键的地方有添加了注释:

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic { if (!dic || dic == (id)kCFNull) return NO; if (![dic isKindOfClass:[NSDictionary class]]) return NO; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)]; if (modelMeta->_keyMappedCount == 0) return NO; if (modelMeta->_hasCustomWillTransformFromDictionary) { dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic]; if (![dic isKindOfClass:[NSDictionary class]]) return NO; } ModelSetContext context = {0}; context.modelMeta = (__bridge void *)(modelMeta); context.model = (__bridge void *)(self); context.dictionary = (__bridge void *)(dic); // 这里做这个处理主要是为了优化性能,哪个少优先处理哪个,提高效率 // 1、属性的映射配置的个数大于json数据元素的个数(if分支),优先处理json数据 // 1.1、遍历json数据的key // 1.2、根据key从`meta->_mapper`配置中寻找映射的`_YYModelPropertyMeta`对象 // 1.3、从json数据中读取值,给属性设置值。 // * 因为`meta->_mapper`保存的是一对一的映射关系,所以另外需要额外的处理keypath映射和数组类型的key的映射 // 2、json数据元素的个数大于属性的映射配置的个数(else分支),优先处理属性属性 // 2.1、从`modelMeta->_allPropertyMetas`读取属性配置 // 2.2、从json数据中读取值,给属性设置值 if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) { // 处理多个属性映射到同一个json数据的key CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context); if (modelMeta->_keyPathPropertyMetas) { // 处理keypath,属性对应json数据key是多层类型的映射 CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } if (modelMeta->_multiKeysPropertyMetas) { // 处理数组类型映射,属性对应json数据key是多个的映射 CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } } else { // 处理属性多json数据的key的一对一映射,这种情况多个属性映射到同一个json数据的key处理方式和ModelSetWithDictionaryFunction方法的不一样,不过最终的结果是一样的 CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas, CFRangeMake(0, modelMeta->_keyMappedCount), ModelSetWithPropertyMetaArrayFunction, &context); } if (modelMeta->_hasCustomTransformFromDictionary) { return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic]; } return YES; } 

以上代码中使用到的两个方法ModelSetWithDictionaryFunctionModelSetWithPropertyMetaArrayFunction,这两个方法很简单,使用获取值方法YYValueForMultiKeysYYValueForKeyPath,根据_YYModelPropertyMeta对象的配置从json数据中获取值,然后调用ModelSetValueForProperty给对象的属性设置值。

static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) { ModelSetContext *context = _context; __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta); __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; }; } 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); } } 

获取值方法YYValueForMultiKeysYYValueForKeyPath也比较简单

  • YYValueForMultiKeys深度遍历json数据取值
  • YYValueForKeyPath广度遍历json数据取值
static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) { id value = nil; for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) { value = dic[keyPaths[i]]; if (i + 1 < max) { if ([value isKindOfClass:[NSDictionary class]]) { dic = value; } else { return nil; } } } return value; } static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) { id value = nil; for (NSString *key in multiKeys) { if ([key isKindOfClass:[NSString class]]) { value = dic[key]; if (value) break; } else { value = YYValueForKeyPath(dic, (NSArray *)key); if (value) break; } } return value; } 

到了最关键的设置属性值的方法ModelSetValueForProperty,使用底层的C语言方法objc_msgSend给属性赋值,该方法区分类型处理值

  • 数值类型
  • NSFoundation类型,包含了NSArray、NSString、NSDictionary、NSData等类型以及对应的可变类型(如果存在)
  • 其余特殊类型,包含了SEL、block、Struct、Union、C指针、字符串指针等

需要注意:

  • 1、可变类型需要特殊处理,需要使用mutableCopy复制一个可变对象赋值给属性,否则使用可变类型的特有api比如addObject就会出现崩溃;
  • 2、json数据取出的值和property定义的值类型可能不一样,如果是数值类型,需要把数值转换为对应类型,否则编译不过;如果是NSFoundation类型,需要把Json数据转换为property定义的类型,否则类型会有问题,方法objc_msgSend可以设置成功,比如property中定义的是属性status_desc是NSString类型,方法objc_msgSend方法的参数传递的是一个NSDate类型,使用status_desc的方法比如length就会崩溃,因为类型变为NSDate了,在NSDate中找不到length方法

方法的部分如下:

static void ModelSetValueForProperty(__unsafe_unretained id model, __unsafe_unretained id value, __unsafe_unretained _YYModelPropertyMeta *meta) { if (meta->_isCNumber) { NSNumber *num = YYNSNumberCreateFromID(value); ModelSetNumberToProperty(model, num, meta); if (num) [num class]; // hold the number } else if (meta->_nsType) { if (value == (id)kCFNull) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil); } else { switch (meta->_nsType) { case YYEncodingTypeNSString: case YYEncodingTypeNSMutableString: { if ([value isKindOfClass:[NSString class]]) { if (meta->_nsType == YYEncodingTypeNSString) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } else { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy); } } else if ([value isKindOfClass:[NSNumber class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (meta->_nsType == YYEncodingTypeNSString) ? ((NSNumber *)value).stringValue : ((NSNumber *)value).stringValue.mutableCopy); } else if ([value isKindOfClass:[NSData class]]) { NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding]; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string); } else if ([value isKindOfClass:[NSURL class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (meta->_nsType == YYEncodingTypeNSString) ? ((NSURL *)value).absoluteString : ((NSURL *)value).absoluteString.mutableCopy); } else if ([value isKindOfClass:[NSAttributedString class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (meta->_nsType == YYEncodingTypeNSString) ? ((NSAttributedString *)value).string : ((NSAttributedString *)value).string.mutableCopy); } } break; // ... 省略很多 default: break; } } } else { BOOL isNull = (value == (id)kCFNull); switch (meta->_type & YYEncodingTypeMask) { case YYEncodingTypeObject: { if (isNull) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil); } else if ([value isKindOfClass:meta->_cls] || !meta->_cls) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value); } else if ([value isKindOfClass:[NSDictionary class]]) { NSObject *one = nil; if (meta->_getter) { one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter); } if (one) { [one yy_modelSetWithDictionary:value]; } else { Class cls = meta->_cls; if (meta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:value]; if (!cls) cls = meta->_genericCls; // for xcode code coverage } one = [cls new]; [one yy_modelSetWithDictionary:value]; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one); } } } break; case YYEncodingTypeClass: { if (isNull) { ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL); } else { Class cls = nil; if ([value isKindOfClass:[NSString class]]) { cls = NSClassFromString(value); if (cls) { ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)cls); } } else { cls = object_getClass(value); if (cls) { if (class_isMetaClass(cls)) { ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)value); } } } } } break; case YYEncodingTypeSEL: { if (isNull) { ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)NULL); } else if ([value isKindOfClass:[NSString class]]) { SEL sel = NSSelectorFromString(value); if (sel) ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)sel); } } break; case YYEncodingTypeBlock: { if (isNull) { ((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())NULL); } else if ([value isKindOfClass:YYNSBlockClass()]) { ((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())value); } } break; case YYEncodingTypeStruct: case YYEncodingTypeUnion: case YYEncodingTypeCArray: { if ([value isKindOfClass:[NSValue class]]) { const char *valueType = ((NSValue *)value).objCType; const char *metaType = meta->_info.typeEncoding.UTF8String; if (valueType && metaType && strcmp(valueType, metaType) == 0) { [model setValue:value forKey:meta->_name]; } } } break; case YYEncodingTypePointer: case YYEncodingTypeCString: { if (isNull) { ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL); } else if ([value isKindOfClass:[NSValue class]]) { NSValue *nsValue = value; if (nsValue.objCType && strcmp(nsValue.objCType, "^v") == 0) { ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, nsValue.pointerValue); } } } // break; commented for code coverage in next line default: break; } } } 

其它重要知识点

位操作

YYModel框架中定义了一个枚举YYEncodingType,这个类型的值可以保存property的值类型(Type Encoding);property的读写、原子、内存等属性(Property Encoding);方法的类型(Method Encoding)(暂时没用到)。在获取类型方法YYEncodingGetType获取类型的时候使用位活操作符"|"组合多个Encoding,在设置属性值的方法比如ModelSetNumberToProperty,使用位与操作符“&”判断是否是特定的类型。使用这种类型有两个好处:1、节省空间;2、位操作效率比较高,比如乘除2,使用左移或者右移的效率会比乘除法高,不过实际编码中为了可读性还是最好使用乘除法,另外现代的编译器会帮我们做很多优化,在绝大多数的场景中,我们可以不用太关注这些细枝末节,真正需要优化的时候才考虑做这类代码上的优化。

信号和锁

YYModel中使用的是信号锁dispatch_semaphore_t,为什么使用这种类型的锁呢,可以从作者的博客中找到答案不再安全的 OSSpinLock ,这种锁的效率还是非常高的,又可以避免OSSpinLock锁产生的问题。这个场景中dispatch_semaphore_t是一个读写互斥锁,CFDictionaryGetValue是读操作,CFDictionarySetValue是写操作,这两者同一时间只能一个进入,其余的需要等待。

+ (instancetype)metaWithClass:(Class)cls { if (!cls) return nil; static CFMutableDictionaryRef cache; static dispatch_once_t onceToken; static dispatch_semaphore_t lock; dispatch_once(&onceToken, ^{ cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); lock = dispatch_semaphore_create(1); }); dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls)); dispatch_semaphore_signal(lock); if (!meta || meta->_classInfo.needUpdate) { meta = [[_YYModelMeta alloc] initWithClass:cls]; if (meta) { dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta)); dispatch_semaphore_signal(lock); } } return meta; } 

CF框架API

yy_modelSetWithDictionary方法中使用到CF框架中容器遍历的API有CFDictionaryApplyFunctionCFArrayApplyFunction,一般滴使用CF框架的API性能会高于NSFoundation框架的API,因为NSFoundation框架的API是基于CF框架的API,此时NSFoundation框架相当于一个中间者,绕了一步。更多的关于CFArray的知识可以参考Exposing NSMutableArray这篇文章的介绍。

原文链接:https://my.oschina.net/FEEDFACF/blog/2054775
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章