iOS runtime (三)(runtime学习之YYModel源码分析)
本文要写的是开源库YYKit其中一个组件YYModel,这个组件的用途就是提供JSON/Dictionary<==>Model这间相互的自动转换。对于它支持些个功能、性能如何、及它是如何提高它的性能可查看YYModel、及YYKit作者的文章,本文章是不会讲这些的,那我这篇文章主要讲什么呢,实现的细节原理,所以贴代码会比较多,并且是以加注释方式,阅读文章同时也要阅读YYModel源码或者贴出来的源码才能理解好,还会根据JSON/Dictionary==>Model这条线,讲解一下代码的流程。写这个目的学习并记录,当然希望也能够帮助到同样想了解YYModel的人更好理解并读懂YYModel。如果对于runtime不熟悉,建议先补充一下runtime相关知识点,可参考我前面的文章runtime分析理解。本文面向的是想了解YYModel内部实现的读者。
数据结构
我们都知道,数据结构决定算法。先来了解一下YYModel的数据结构。里面文件不多就NSObject+YYModel.h、NSObject+YYModel.m、YYClassInfo.h、YYClassInfo.m。先说YYClassInfo.h和YYClassInfo.m中的类
YYClassInfo它的类定义是这样
这里省略了它的方法,只留下它的属性。YYClassInfo保存了一个类(类对象,而不是实例对象)的类变量cls、父类变量superCls、元类metaCls、是否为元类isMeta、类名称、父类的YYClassInfo指针 superClassInfo、所有成员变量信息ivarInfos、所有方法信息methodInfos、所有属性信息propertyInfos。cls、superCls、metaCls、isMeta都比较简单,ivarInfos、methodInfos在YYModel中其实是不会用到,所以这里我们重点关重superClassInfo、propertyInfos。让我们来看一下,它是怎么建立并存储一个类以及它父类一直来顶层的NSObject类的属性信息。建立的入口的类方法classInfoWithClass:
可以看到,这段逻辑先从缓存看能不能拿到cls的YYClassInfo,如果拿到,直接返回,拿不到就去创建并获取YYClassInfo的信息,取到后就缓存起来。再看创建获取YYClassInfo的方法initWithClass:
看注释,这段代码不难理解,接下来就是_update方法,就是在这个方法内获取类的ivarInfos、methodInfos、propertyInfos。但是就如前面所说,我们只需关注propertyInfos。里面保存的是YYClassPropertyInfo,它的结构如下:
每个YYClassPropertyInfo实例对象就代表了类的一个属性,只不过它保存了更多信息,保存的信息里面我们看到有YYEncodingType type,这个其实就是对
NSString *typeEncoding的一个转换,转换成作者自定义可以快带使用的枚举,具体意义见YYEncodingType。为什么要保存属性的setter和getter,在作者的文章中说到:Key-Value Coding 使用起来非常方便,但性能上要差于直接调用 Getter/Setter,所以如果能避免 KVC 而用 Getter/Setter 代替,性能会有较大提升。
此时看回YYClassInfo的_update方法,里面这段
这里就是遍历类的所有属性,以属性作为参数,传给YYClassPropertyInfo,在其内部获取YYClassPropertyInfo所需信息,里面细节就不再细说。总的来说,类的信息YYClassInfo都被缓存的起来,并前通过superClassInfo 指针,建立了一个关系链,使得通过一个类就能拿到它自己以它一直往上所有父类的YYClassInfo信息。
接下来是NSObject+YYModel.h、NSObject+YYModel.m。其中y主要是_YYModelMeta、_YYModelPropertyMeta以下为它们的定义:
_YYModelPropertyMeta是跟_YYClassPropertyInfo一一对应的,只不过它多了_mappedToKey,_mappedToKeyPath,_mappedToKeyArray,和其它一些成员。这里举个列子就明白了
这个Book,它就会有四个_YYClassPropertyInfo。前两个情况是一样的,_mappedToKey值为 "n"、"p",第三个_mappToKeyPath值为"ext.desc",第四个_mappedToKeyArray值是@[@"id",@"ID",@"book_id"]。所以如果有以下json
// JSON: { "n":"Harry Pottery", "p": 256, "ext" : { "desc" : "A book written by J.K.Rowing." }, "ID" : 100010 }
YYModel就能根据_YYClassPropertyInfo中_mappedToKey,_mappedToKeyPath,_mappedToKeyArray拿到json里面的值,并设置到Book的相应属性中。
接下来,就是_YYModelMeta。它里面有四个成员,它们都是容器,里面都是保存_YYClassPropertyInfo,但是还是有所区别
_allPropertyMetas:这个保存了所有从YYClassInfo在其继承关系中的所有属性propertyInfo转换得来的_YYClassPropertyInfo,只不过它是最原始的,还没有与json中的key做任何关联,即_YYClassPropertyInfo中的_mappedToKey,_mappedToKeyPath,_mappedToKeyArray都还是为nil。
_mapper:这个是在_allPropertyMetas基础上已经建立好与json的关系的,即_mappedToKey,_mappedToKeyPath,_mappedToKeyArray至少有一个不为空的。
_keyPathPropertyMetas:这个只保存_mappedToKeyPath不为空的所有_YYClassPropertyInfo。
_multiKeysPropertyMetas:这个只保存_mappedToKeyArray不为空的所有_YYClassPropertyInfo。
另外还有容器类属性、及黑名单与白名单逻辑,这两个比较简单,不展开。下面是它建立映射关系的实现代码:
- (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; } } //创建所有属性的metas,以名字作为key // Create all property metas. 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 NSMutableDictionary *mapper = [NSMutableDictionary new]; NSMutableArray *keyPathPropertyMetas = [NSMutableArray new]; NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new]; //当自定义属性对应关系才会走这,也就是应用层属性名与返回json的key是不一样时,根据modelCustomPropertyMapper上注释的例子写的算法,生成对应的数据结构 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]; //NSString 两种情况,1、简单的key对应@"name" : @"n", 2、keyPath方法:@"desc" : @"ext.desc" 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]; } propertyMeta->_next = mapper[mappedToKey] ?: nil; //检查是否有同样key对应多个属性,这里有个小技巧,当mapper[mappedToKey]指向了当前最新那个propertyMeta,前一个mapper[mappedToKey]被记录到了当前propertyMeta->_next里面了,所以要读取到所到相同mappedToKey的propertyMeta时,只要mapper[mappedToKey],mapper[mappedToKey]->_next,不停遍历,直到nil即可 mapper[mappedToKey] = propertyMeta; } //一个属性,对应多个不同json里的key时,如:@"bookID": @[@"id", @"ID", @"book_id"] 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]; propertyMeta->_next = mapper[mappedToKey] ?: nil; mapper[mappedToKey] = propertyMeta; } }]; } //在allPropertyMetas剩余下来的只要简单做一遍关联即可,因为什么有自定义关联 [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) { propertyMeta->_mappedToKey = name; propertyMeta->_next = mapper[name] ?: nil; mapper[name] = propertyMeta; }]; if (mapper.count) _mapper = mapper; if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas; //keypath 类型的 if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas; //对应多个json key类型的。 _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; }
到此,主要的数据结构已经介绍完成了。
JSON/Dictionary==>Model
下面就是json自动转换成Model的关键入口,已经带注释
- (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); /*使用CFDictionaryApplyFunction方式提高迭代遍历的性能*/ if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) { //如果model的数据量多于json数据量,迭代遍历json的进行赋值,减少不必要的迭代 CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context); //上面只会迭代简单的key映射关系的,所以这里分别还有做对于keyPath及,keyArray关联的迭代 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 { //如果model的数据量少于json数据量, 迭代遍历model的数据进行赋值, CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas, CFRangeMake(0, modelMeta->_keyMappedCount), ModelSetWithPropertyMetaArrayFunction, &context); } //如果有自定义转换方法,前面所做的都会丢弃 if (modelMeta->_hasCustomTransformFromDictionary) { return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic]; } return YES; }
接着就会来到函数ModelSetWithPropertyMetaArrayFunction中,下面继续见代码
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); //拿到propertyMeta,然后分别根据_mappedToKeyArray或_mappedToKeyPath或_mappedToKey在json dictionary中去拿到值 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,这个函数相当长,但是原理都是差不多的,所以下面只会讲解两种情况,剩下的有兴趣自己可以继续研究。
1、当要设置的属性是个C语言的基础数据类型,其实就是从_YYModelPropertyMeta中拿到setter,再根据YYEncodingType取NSNumber中的值然后设置进去,下面就省略掉一些case。
static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model, __unsafe_unretained NSNumber *num, __unsafe_unretained _YYModelPropertyMeta *meta) { switch (meta->_type & YYEncodingTypeMask) { case YYEncodingTypeBool: { ((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue); } break;
... ...
case YYEncodingTypeLongDouble: { long double d = num.doubleValue; if (isnan(d) || isinf(d)) d = 0; ((void (*)(id, SEL, long double))(void *) objc_msgSend)((id)model, meta->_setter, (long double)d); } // break; commented for code coverage in next line default: break; } }
2、如果要设置的属性为自定义类类型,请看以下代码,只取片段
switch (meta->_type & YYEncodingTypeMask) { //如果property是个自定义对类,即继承自NSObject case YYEncodingTypeObject: { //如果拿到对应json为kCFNull,property被设置为nil if (isNull) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil); } else if ([value isKindOfClass:meta->_cls] || !meta->_cls) { //如果从json中拿到的值,已经是property的cls,直接设置 ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value); } else if ([value isKindOfClass:[NSDictionary class]]) { //如果从json中拿到的值是个字典 NSObject *one = nil; if (meta->_getter) { //从getter中能拿到实例 one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter); } if (one) { //拿到了就递归调用yy_modelSetWithDictionary [one yy_modelSetWithDictionary:value]; } else { //否则,先查看用户是否对此类有自定义的类联,目的是解决这个是个基类指针,要根据value里面的值创建不同有子类,详情看modelCustomClassForDictionary声明 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;
......
}
就是根据这样的思路,无论是什么类型,有的需要递归,有的不需递归。这样就实现了自动json值被设置到了model中去了。这里省了很多情况,如属性是容器类型是如何自定义关联里面元素,是结构体等等。还有JSON/Dictionary==>Model的流程又是怎么样。这里就不继续往下了,因为实在又长又臭了已经。相信如果前面说的内容都能理解了,有了这样的基础,要理解全部细节并不会很困难。
出处:http://www.cnblogs.com/chenxianming/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任权利。
posted on 2016-07-04 16:25 chenxianming 阅读(2235) 评论(0) 编辑 收藏 举报