内容简介:YYModel库中涉及到Runtime、CF API、信号和锁、位的操作。学习该库可以学习到使用Runtime获取类的信息,包括:类属性信息、类ivar信息、类方法、类型编码;使用runtime底层技术进行方法调用,也就是objc_msgSend方法的使用;dispatch_semaphore_t信号锁的使用;CF框架中CFMutableDictionaryRef/CFMutableDictionaryRef对象的操作;位的操作。首先会介绍下YYModel的使用,作为下面类属性的定义和json数据中的ke
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
转换后的结果如下:

自定义属性和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
转换后的结果如下:

黑名单和白名单配置
黑名单配置如下:
@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
反序列化的结果,配置在黑名单中的属性( sourceText
和 shareCount
)不会被赋值

白名单配置如下:
@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
反序列化的结果,只有配置在白名单中的属性( sourceText
和 shareCount
)才会被赋值

类型映射配置
类型映射配置用于属性是数组类型,需要配置该属性中元素的类类型,比如我们定义的数据模型如下,有个 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
反序列化的结果如下:
预备知识
首先需要了解的是苹果对于类型说明的的一些规范,包括了 Type Encodings 和 Property 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
这种结构体类型的,已经解析好了 name
和 value
,方便使用。
假设定义有如下的属性 @property(getter=isIntReadOnlyGetter, readonly) int intReadonlyGetter;
,使用 property_getAttributes
方法获取到的属性对应的属性描述如下: Ti,R,GisIntReadOnlyGetter
, intReadonlyGetter
属性对应的 objc_property_attribute_t
类型为 p
,基本的规则解释如下:
Type Encodings
以下是一段使用 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 String 和 Type 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信息。实际上
、
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_copyMethodList
、 class_copyPropertyList
、 class_copyIvarList
涉及到内存的拷贝问题,使用完成之后需要使用C的方法free释放拷贝的内容,防止内存泄漏。上面有提到说
、
这两个信息并没有真正的使用到,接下来会着重的介绍属性信息的获取中的一些细节。
- (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 Encodings 和 Property 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分支),优先处理属性属性
modelMeta->_allPropertyMetas
对应的代码如下,关键的地方有添加了注释:
- (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; }
以上代码中使用到的两个方法 ModelSetWithDictionaryFunction
、 ModelSetWithPropertyMetaArrayFunction
,这两个方法很简单,使用获取值方法 YYValueForMultiKeys
、 YYValueForKeyPath
,根据 _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); } }
获取值方法 YYValueForMultiKeys
、 YYValueForKeyPath
也比较简单
YYValueForMultiKeys YYValueForKeyPath
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有 CFDictionaryApplyFunction
和 CFArrayApplyFunction
,一般滴使用CF框架的API性能会高于NSFoundation框架的API,因为NSFoundation框架的API是基于CF框架的API,此时NSFoundation框架相当于一个中间者,绕了一步。更多的关于CFArray的知识可以参考 Exposing NSMutableArray 这篇文章的介绍。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Egret——HTML5游戏开发指南
张鑫磊 等 / 电子工业出版社 / 2016-3 / 85
《Egret——HTML5游戏开发指南》由浅入深,在讲解游戏开发基础的同时提供众多实战案例供读者学习。《Egret——HTML5游戏开发指南》章节内容包含Egret基础概念及基础图形图像处理方法、网络相关操作、移动设备适配、性能优化、文本动画相关知识、调试技巧、DragonBones骨骼动画系统和P2物理引擎等。通过《Egret——HTML5游戏开发指南》,读者可以了解并掌握HTML5游戏开发技能......一起来看看 《Egret——HTML5游戏开发指南》 这本书的介绍吧!