YYModel,相当精简,一个YYClassInfo
类,一个NSObject+YYModel
的分类。
YYClassInfo
是YYModel对类中属性、成员变量、方法和类属性做的一层封装,后面详解会提到。
NSObject+YYModel
工作,就是YYModel的主体工作——JSON转模型 。
从实例开始 下面是一个实例:
1 2 3 4 5 6 YYBook *book = [YYBook modelWithJSON:@" \ { \ \"name\": \"Harry Potter\", \ \"pages\": 512, \ \"publishDate\": \"2010-01-01\" \ }" ];
NSObject (YYModel)中,-(instancetype)modelWithJSON:(id)json
分两步:
1 2 3 4 + (instancetype )modelWithJSON:(id )json { NSDictionary *dic = [self _yy_dictionaryWithJSON:json]; return [self modelWithDictionary:dic]; }
(1) 将JSON转换为字典;
_yy_dictionaryWithJSON:
将JSON转换为字典。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 + (NSDictionary *)_yy_dictionaryWithJSON:(id )json { if (!json || json == (id )kCFNull) return nil ; NSDictionary *dic = nil ; NSData *jsonData = nil ; if ([json isKindOfClass:[NSDictionary class ]]) { dic = json; } else if ([json isKindOfClass:[NSString class ]]) { jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding ]; } else if ([json isKindOfClass:[NSData class ]]) { jsonData = json; } if (jsonData) { dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL ]; if (![dic isKindOfClass:[NSDictionary class ]]) dic = nil ; } return dic; }
1 2 3 4 5 6 7 8 9 10 11 12 13 + (instancetype )modelWithDictionary:(NSDictionary *)dictionary { if (!dictionary || dictionary == (id )kCFNull) return nil ; if (![dictionary isKindOfClass:[NSDictionary class ]]) return nil ; Class cls = [self class ]; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]; if (modelMeta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:dictionary] ?: cls; } NSObject *one = [cls new]; if ([one modelSetWithDictionary:dictionary]) return one; return nil ; }
字典转模型过程如下:
比如,要初始化一个Shape类对象,根据是否有对应字段来生成不同的子类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @class YYCircle , YYRectangle , YYLine ;@implementation YYShape + (Class)modelCustomClassForDictionary:(NSDictionary *)dictionary { if (dictionary[@"radius" ] != nil ) { return [YYCircle class ]; } else if (dictionary[@"width" ] != nil ) { return [YYRectangle class ]; } else if (dictionary[@"y2" ] != nil ) { return [YYLine class ]; } else { return [self class ]; } } @end
1 2 NSObject *one = [cls new];if ([one modelSetWithDictionary:dictionary]) return one;
字典转模型 - (BOOL)modelSetWithDictionary:(NSDictionary *)dic
方法是NSObject+YYModel
的一个方法,支持所有继承自NSObject对象的类。
在这里,作了省略,因为我们在给模型赋值,需要先对这个类进行大解剖。之后我们再看如何赋值。
1 2 3 4 5 6 7 8 9 - (BOOL )modelSetWithDictionary:(NSDictionary *)dic { _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self )]; .... return YES ; }
获取类信息
给模型赋值
获取类信息 方法调用链
_YYModelMeta,简单看一下这个类,这个类的目的是存储一个类的所有信息,包括属性、方法和成员变量等。
而我们能进行JSON转模型(即类对象)则依赖于如何获取该类对象中所有信息,才能对类型赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + (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; }
这里有两个知识点:
Core Foundation字典
1 2 3 static CFMutableDictionaryRef cache = CFDictionaryCreateMutable (CFAllocatorGetDefault (), 0 , &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); _YYModelMeta *meta = CFDictionaryGetValue (cache, (__bridge const void *)(cls)); CFDictionarySetValue (cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
其中关于创建CF字典的,更多可以参考Objective-C 语言(五)系统框架
信号量机制
创建一个信号量:
1 2 static dispatch_semaphore_t lock; lock = dispatch_semaphore_create(1 );
信号量控制
1 2 dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); dispatch_semaphore_signal(lock);
dispatch_semaphore_wait
等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1。
dispatch_semaphore_signal
信号量+1,表示增加一个可用资源。
更多关于信号量的内容可以参考 多线程(七)锁
在上面代码中,我们看到了两个加锁过程:分别是CFDictionaryGetValue
和CFDictionarySetValue
,即在读写是都进行了加锁控制。
类信息的初始化 根据上面调用,进入到类信息的初始化:
meta = [[_YYModelMeta alloc] initWithClass:cls];
1 - (instancetype )initWithClass:(Class)cls;
我们来看这个方法中做了哪些工作(为体现主要流程,做了删减):
1 2 3 4 5 6 7 8 9 10 11 12 - (instancetype )initWithClass:(Class)cls { YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls]; ..... ..... return self ; }
在这个方法里主要做了以下动作:
读取类信息:*YYClassInfo classInfo = [YYClassInfo classInfoWithClass:cls] ;
class info包装成_YYModelMeta对象;
1. 读取类信息 首先来看:_YYModelMeta
类中initWithClass
方法里的第一行代码就是调用YYClassInfo
的classInfoWithClass
。
追溯下去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + (instancetype )classInfoWithClass:(Class)cls { if (!cls) return nil ; static CFMutableDictionaryRef classCache; static CFMutableDictionaryRef metaCache; static dispatch_once_t onceToken; static dispatch_semaphore_t lock; dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); YYClassInfo *info = CFDictionaryGetValue (class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls)); if (info && info->_needUpdate) { [info _update]; } dispatch_semaphore_signal(lock); if (!info) { info = [[YYClassInfo alloc] initWithClass:cls]; if (info) { } } return info; }
我们这里先忽略缓存的策略。关注:
1 info = [[YYClassInfo alloc] initWithClass:cls];
下面是YYClassInfo
类的initWithClass
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - (instancetype )initWithClass:(Class)cls { if (!cls) return nil ; self = [super init]; _cls = cls; _superCls = class_getSuperclass(cls); _isMeta = class_isMetaClass(cls); if (!_isMeta) { _metaCls = objc_getMetaClass(class_getName(cls)); } _name = NSStringFromClass (cls); [self _update]; _superClassInfo = [self .class classInfoWithClass:_superCls]; return self ; }
根据YYModel
对YYClassInfo
的设定,该类是存储一个类对象所有信息的类,其声明:
1 2 3 4 5 6 7 8 9 @property (nonatomic , assign , readonly ) Class cls; @property (nullable , nonatomic , assign , readonly ) Class superCls; @property (nullable , nonatomic , assign , readonly ) Class metaCls; @property (nonatomic , readonly ) BOOL isMeta; @property (nonatomic , strong , readonly ) NSString *name; @property (nullable , nonatomic , strong , readonly ) YYClassInfo *superClassInfo; @property (nullable , nonatomic , strong , readonly ) NSDictionary <NSString *, YYClassIvarInfo *> *ivarInfos; @property (nullable , nonatomic , strong , readonly ) NSDictionary <NSString *, YYClassMethodInfo *> *methodInfos; @property (nullable , nonatomic , strong , readonly ) NSDictionary <NSString *, YYClassPropertyInfo *> *propertyInfos;
从上面的方法中以及YYClassInfo
可以看出,获取了父类、元类以及类名等信息。其中关键的方法在_update
方法中,以下做简单分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - (void )_update { _ivarInfos = nil ; _methodInfos = nil ; _propertyInfos = nil ; Class cls = self .cls; ..... .... if (!_ivarInfos) _ivarInfos = @{}; if (!_methodInfos) _methodInfos = @{}; if (!_propertyInfos) _propertyInfos = @{}; _needUpdate = NO ; }
从上面可以看到,读取每一部分信息后,保存在了字典内,我们选取成员变量的读取来作解析:
1 2 3 4 5 6 7 8 9 10 11 12 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); }
这一段的方法的重点在:class_copyMethodList ,该方法是runtime中获取class method的方法。
1 2 3 4 @param cls The class you want to inspect. @param outCount On return , contains the length of the returned array. If outCount is NULL , the length is not returned. Method * class_copyMethodList(Class cls, unsigned int * outCount)
针对每一部分的信息,都有对应的类,分别为:YYClassMethodInfo
,YYClassPropertyInfo
,YYClassIvarInfo
,而且每个类中都有对应的初始化方法。
其中关于这三部分信息,可以参考下面**Method信息 、Property信息 、Ivar信息 **的部分,以及Objective-C(八)对象的本质及分类 获取更多的基础知识。
经过上面处理之后,我们可以得到如下YYBook类的Class Info:
等上面的class info处理完成之后,回到 [_YYModelMeta initWithClass:]
方法中,该方法将class info,对各种YYModel对外提供的接口进行整合处理。比如黑名单、白名单、自定义mapper等接口。
处理完成之后的_YYModelMeta对象才是我们之后进行赋值任务的主要承载着。
那么来看下这部分处理,有点长:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 - (instancetype )initWithClass:(Class)cls { YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls]; if (!classInfo) return nil ; self = [super init]; NSSet *blacklist = nil ; if ([cls respondsToSelector:@selector (modelPropertyBlacklist)]) { NSArray *properties = [(id <YYModel>)cls modelPropertyBlacklist]; if (properties) { blacklist = [NSSet setWithArray:properties]; } } NSSet *whitelist = nil ; if ([cls respondsToSelector:@selector (modelPropertyWhitelist)]) { NSArray *properties = [(id <YYModel>)cls modelPropertyWhitelist]; if (properties) { whitelist = [NSSet setWithArray:properties]; } } 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; } } NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new]; YYClassInfo *curClassInfo = classInfo; while (curClassInfo && curClassInfo.superCls != nil ) { 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; 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]; } 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]; propertyMeta->_next = mapper[mappedToKey] ?: nil ; mapper[mappedToKey] = propertyMeta; } }]; } [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; 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 ; }
代码中作了详尽的注释,简而言之,得出如下等式:
类本身信息 + 用户自定义部分 = _YYModelMeta
其中,_YYModelMeta 是后面model赋值的主要数据源。
Method信息 YYClassMethodInfo
类的声明:
1 2 3 4 5 6 7 8 @property (nonatomic , assign , readonly ) Method method; @property (nonatomic , strong , readonly ) NSString *name; @property (nonatomic , assign , readonly ) SEL sel; @property (nonatomic , assign , readonly ) IMP imp; @property (nonatomic , strong , readonly ) NSString *typeEncoding; @property (nonatomic , strong , readonly ) NSString *returnTypeEncoding; @property (nullable , nonatomic , strong , readonly ) NSArray <NSString *> *argumentTypeEncodings;
简单做一下说明:
Method类型
typedef struct objc_method *Method;
在objc-runtime-old.h文件中声明如下:
1 2 3 4 5 struct old_method { SEL method_name; char *method_types; IMP method_imp; };
那么我们就可以理解name
,SEL
,IMP
属性了。
其中,对于SEL
和IMP
这两个概念,需要区别:
SEL
1 2 typedef struct objc_selector *SEL;
Objective-C是动态语言,动态体现在可以在运行的时候修改所执行的方法,可以把一个对象的所有方法看成一张表,SEL就可以看成表中每一条的索引,根据方法名来生成对应的SEL,所以OC中不同的方法名就对应不同的方法
1 2 SEL1 | SEL2 | SEL3 ... IMP1 | IMP2 | IMP3 ...
IMP
1 2 A pointer to the function of a method implementation. typedef id (*IMP)(id , SEL, ...);
IMP是真正的函数指针,指向函数的实现
这里,我们用YYBook
来说明其中的Method
信息,如下为其声明文件:
1 2 3 4 5 @interface YYBook : NSObject @property (nonatomic , copy ) NSString *name;@property (nonatomic , assign ) uint64_t pages;@property (nonatomic , strong ) NSDate *publishDate;@end
但是,我们并没有发现有任何方法声明,但是,我们需要注意的是在Objective-C 2.0中,属性会自动为我们生成属性对应成员变量的setter与getter方法,这些方法并不需要手动书写,但是却在编译时在类信息中。
我们选择属性的pages
来一窥Method
的信息:
首先,pages
的getter/setter方法,其声明应该是:
1 2 - (int )pages; - (void )setPages:(int )pages;
针对方法1,没有参数,有返回值,且返回值为int类型。方法2呢,没有返回值,但是包含一个int类型的参数。
下面,我们看YYClassMethodInfo
的初始化方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 - (instancetype )initWithMethod:(Method)method { if (!method) return nil ; self = [super init]; _method = method; _sel = method_getName(method); _imp = method_getImplementation(method); const char *name = sel_getName(_sel); if (name) { _name = [NSString stringWithUTF8String:name]; } const char *typeEncoding = method_getTypeEncoding(method); if (typeEncoding) { _typeEncoding = [NSString stringWithUTF8String:typeEncoding]; } char *returnType = method_copyReturnType(method); if (returnType) { _returnTypeEncoding = [NSString stringWithUTF8String:returnType]; free(returnType); } unsigned int argumentCount = method_getNumberOfArguments(method); if (argumentCount > 0 ) { NSMutableArray *argumentTypes = [NSMutableArray new]; for (unsigned int i = 0 ; i < argumentCount; i++) { char *argumentType = method_copyArgumentType(method, i); NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil ; [argumentTypes addObject:type ? type : @"" ]; if (argumentType) free(argumentType); } _argumentTypeEncodings = argumentTypes; } return self ; }
关注几个runtime的方法:
1 2 3 4 5 6 * OBJC_EXPORT SEL method_getName(Method m) * OBJC_EXPORT IMP method_getImplementation(Method m) * OBJC_EXPORT const char *sel_getName(SEL sel) * OBJC_EXPORT const char *method_getTypeEncoding(Method m) * OBJC_EXPORT unsigned int method_getNumberOfArguments(Method m) * OBJC_EXPORT char *method_copyArgumentType(Method m, unsigned int index)
根据这些方法,我们能获取Method的大部分重要信息。对pages的getter方法:
其中,需要关注的是:
unsigned int argumentCount = method_getNumberOfArguments(method); //参数的个数
1 2 3 4 5 6 7 8 9 10 if (argumentCount > 0 ) { NSMutableArray *argumentTypes = [NSMutableArray new]; for (unsigned int i = 0 ; i < argumentCount; i++) { char *argumentType = method_copyArgumentType(method, i); NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil ; [argumentTypes addObject:type ? type : @"" ]; if (argumentType) free(argumentType); } _argumentTypeEncodings = argumentTypes; }
这个代码,是处理方法参数类型的,在debug模式下,我们获取到的argumentCount
=2,可是根据方法声明,- (int)pages;
并没有参数!
这就要从Objective-C中方法调用说起,在OC中,方法调用,也叫给对象发送消息,发送消息最后都会调用下面这个函数:
1 id objc_msgSend(id self , SEL op, ...)
可以看出来,除了真正的参数之外,还有两个参数是默认的,一个是对象本身self
,另外一个是方法的SEL。所以刚才获取到的参数为2,是正确的。而在- (int)pages;
中,参数的类型为:
[@”@”,@”:”]
前面@”@”指代该参数为self,即对象为id类型,@”:”,指代类型是SEL类型。关于type encoding,参考构建iOS-Model层(二)类型解析 。
下面,看一下- (void)setPages:(int)pages;
方法对应的初始化过程。
图二
这里有一点可以继续深挖的是,我们在YYBook
中声明了三个属性,但是获取其方法却有七个,即:
Method *methods = class_copyMethodList(cls, &methodCount);
其中,methodCount为7,按道理应该是6。调试发现,还有一个方法如下:
图三
看方法名,是销毁方法 。
Property信息 有了前面关于Method的铺垫,Property其实也是一致的。所以,下面是流水记录:
1 2 3 4 5 6 7 8 9 10 @interface YYClassPropertyInfo : NSObject @property (nonatomic , assign , readonly ) objc_property_t property; @property (nonatomic , strong , readonly ) NSString *name; @property (nonatomic , assign , readonly ) YYEncodingType type; @property (nonatomic , strong , readonly ) NSString *typeEncoding; @property (nonatomic , strong , readonly ) NSString *ivarName; @property (nullable , nonatomic , assign , readonly ) Class cls; @property (nonatomic , assign , readonly ) SEL getter ; @property (nonatomic , assign , readonly ) SEL setter ; @end
这部分,更多可以参考:
构建iOS Model层 系列文章
Ivar信息 1 2 3 4 5 6 7 @interface YYClassIvarInfo : NSObject @property (nonatomic , assign , readonly ) Ivar ivar; @property (nonatomic , strong , readonly ) NSString *name; @property (nonatomic , assign , readonly ) ptrdiff_t offset; @property (nonatomic , strong , readonly ) NSString *typeEncoding; @property (nonatomic , assign , readonly ) YYEncodingType type;
在初始化过程中,依次:
_name成员变量:
_pages成员变量:
_publishDate成员变量:
这里,又有一个可以深究的点 ,看下图:
发现,没有,这里还有一个特俗的成员变量isa
。至于这个为何物,自己去寻吧 。
给模型赋值 经过上面这些步骤之后拿到的类的所有信息,需要将这些信息用于字典转模型的过程中。
回到字典转模型 NSObject+YYModel
分类中调用的的方法- (BOOL)modelSetWithDictionary:(NSDictionary *)dic
,这里,仍然会将部分细节省略,只关注主要节点流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 - (BOOL )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); if (modelMeta->_keyMappedCount >= CFDictionaryGetCount ((CFDictionaryRef )dic)) { CFDictionaryApplyFunction ((CFDictionaryRef )dic, ModelSetWithDictionaryFunction, &context); if (modelMeta->_keyPathPropertyMetas) { CFArrayApplyFunction ((CFArrayRef )modelMeta->_keyPathPropertyMetas, CFRangeMake (0 , CFArrayGetCount ((CFArrayRef )modelMeta->_keyPathPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } if (modelMeta->_multiKeysPropertyMetas) { CFArrayApplyFunction ((CFArrayRef )modelMeta->_multiKeysPropertyMetas, CFRangeMake (0 , CFArrayGetCount ((CFArrayRef )modelMeta->_multiKeysPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } } else { CFArrayApplyFunction ((CFArrayRef )modelMeta->_allPropertyMetas, CFRangeMake (0 , modelMeta->_keyMappedCount), ModelSetWithPropertyMetaArrayFunction, &context); } if (modelMeta->_hasCustomTransformFromDictionary) { return [((id <YYModel>)self ) modelCustomTransformFromDictionary:dic]; } return YES ; }
在这个方法里,调用的一个方法是:
1 2 3 4 CFDictionaryApplyFunction ((CFDictionaryRef )dic, ModelSetWithDictionaryFunction, &context);
CFDictionaryApplyFunction
方法会对字典每个元素执行一个自定义的方法。在这里,这个方法就是:
1 2 3 4 5 6 7 8 9 10 11 12 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; }; }
以上就是给模型属性赋值的核心 !
可以看到,所有的类的信息,都封装在结构体ModelSetContext
中:
1 2 3 4 5 typedef struct { void *modelMeta; ///< _YYModelMeta void *model; ///< id (self) void *dictionary; ///< NSDictionary (json) } ModelSetContext;
剩下的,我们到了最后一步,方法 ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
这个方法里做的唯一工作,就是从字典取出值赋给Model。而且,直接使用了objc_msgSend
方法来进行赋值。
下面是针对属性为NSDate类型的赋值:
1 2 3 4 5 6 7 case YYEncodingTypeNSDate: { if ([value isKindOfClass:[NSDate class ]]) { ((void (*)(id , SEL, id ))(void *) objc_msgSend)((id )model, meta->_setter , value); } else if ([value isKindOfClass:[NSString class ]]) { ((void (*)(id , SEL, id ))(void *) objc_msgSend)((id )model, meta->_setter , YYNSDateFromString(value)); } } break ;
可以看到,
objc_msgSend)((id)model, meta->_setter, value)
中,model,即一步一步传导下来的即将要赋值的model对象,meta->_setter则是通过YYModel一步一步解析出来的setter方法,value则是通过JSON转为字典后对应该属性中的value。
如此,我们就完成了属性的赋值。
也许,你还有疑问:CFArrayApplyFunction
这个函数调用在if分支中的作用,在这里简单作一下说明:
假如用户自定义mapper如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @interface YYMessage : NSObject @property (nonatomic , assign ) uint64_t messageId;@property (nonatomic , strong ) NSString *content;@property (nonatomic , strong ) NSDate *time;@property (nonatomic ,copy ) NSString *name;@end + (NSDictionary *)modelCustomPropertyMapper { return @{@"messageId" :@[@"id" , @"ID" , @"mes_id" ], @"time" :@"t" , @"name" :@"user.name" }; }
这里,出现了以下两种情况:
属性对应了多个key,比如属性messageId,可以解析“id”,“ID”,“mes_id”三种key,即服务器返回的JSON中假如有这三种key之一,都支持解析。
属性是key path,如user.name。
关于这部分的信息,其实都在类信息的初始化-class info包装成_YYModelMeta对象 中处理完成。
在自定义mapper下,我们YYMessage的类信息如下:
YYMessage类信息的_YYModelMeta
YYMessage class info
YYMessage 自定义mapper中的key path。
YYMessage 自定义mapper中的key path。
至此,我们完结了,这一篇摘要。
然而,YYModel还有许多特性,值得我们去摸索。
系列
解码YYModel(一)基础
解码YYModel(二)特性
解码YYModel(三)参考