YYModel 学习以及源码阅读
准备工作
- 1.文件地址
- Download 下载后查看
- 我们可以先看文件中的readMe部分可以查看到简单使用部分和一些举例。如:
- 举例说明了将JSON转成MODEL 和 重新将MODEL转成JSON。
- 现在的目标就是进入源文件中查看源码,看看具体代码是怎么写的然后学习一下。
常用方法归纳
- 1.yy_modelWithJSON : JSON转成为 MODEL。
- User *user = [User yy_modelWithJSON:json];
- 2.yy_modelToJSONObject:MODEL转成为字典
- NSDictionary *json = [user yy_modelToJSONObject];
- 3.模型中的key和字典中的key,会自动匹配,如果自动匹配失败,这个值就会忽略。
- 这个就是手动匹配。说明字典中的key和model中的key对应。
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"name" : @"n",
@"page" : @"p",
@"desc" : @"ext.desc",
@"bookID" :@[@"id",@"ID",@"book_id"]};
}
- 4.模型中套模型也会自动匹配。嵌套模型
- 5.容器属性 如数组、集合、可变字典使用
+ (NSDictionary *)modelContainerPropertyGenericClass;方法 - 6.黑名单和白名单
+ (NSArray *)modelPropertyBlacklist
+ (NSArray *)modelPropertyWhitelist
- 7.数据校验和自定义转换。字典转化成模型后可以调用这个方法,校验数据是否正确,如果正确则返回YES,错误则返回NO。
// JSON:
{
"name":"Harry",
"timestamp" : 1445534567
}
// Model:
@interface User
@property NSString *name;
@property NSDate *createdAt;
@end
@implementation User
// 当 JSON 转为 Model 完成后,该方法会被调用。
// 你可以在这里对数据进行校验,如果校验不通过,可以返回 NO,则该 Model 会被忽略。
// 你也可以在这里做一些自动转换不能完成的工作。
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic {
NSNumber *timestamp = dic[@"timestamp"];
if (![timestamp isKindOfClass:[NSNumber class]]) return NO;
_createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue];
return YES;
}
// 当 Model 转为 JSON 完成后,该方法会被调用。
// 你可以在这里对数据进行校验,如果校验不通过,可以返回 NO,则该 Model 会被忽略。
// 你也可以在这里做一些自动转换不能完成的工作。
- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic {
if (!_createdAt) return NO;
dic[@"timestamp"] = @(n.timeIntervalSince1970);
return YES;
}
@end
常规方法的源码查看与解析
主要在这个类中就可以看到这些方法的具体实现。
yy_modelWithJSON
通常在使用YYModel时,需要将JSON转为Model,涉及到以下三个方法。
@interface NSObject (YYModel)
+ (nullable instancetype)yy_modelWithJSON:(id)son;
@end
@interface NSArray (YYModel)
+ (nullable NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json;
@end
@interface NSDictionary (YYModel)
+ (nullable NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json;
@end
- 看出来是先将json转成为字典,再将字典转化为模型。
- _yy_dictionaryWithJSON : json id 类型转字典
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
if (!json || json == (id)kCFNull) return nil; //判断空,如果为空则返回nil.
NSDictionary *dic = nil;
NSData *jsonData = nil;
if ([json isKindOfClass:[NSDictionary class]]) { //判断是否为字典如果是则直接赋值。
dic = json;
} else if ([json isKindOfClass:[NSString class]]) { //判断是否为字符串,是则直接字符串转NSData。
jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
} else if ([json isKindOfClass:[NSData class]]) {//直接赋值data
jsonData = json;
}
if (jsonData) {
//拿到data后通过方法 data转字典
dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
//如果最后json解析还是失败了就设为空。
if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
}
//最后返回字典
return dic;
}
- 开始对这个字典转模型了, 以上三个方法最终都会调用这个方法。
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
//1.常规判断字典是否为空
if (!dictionary || dictionary == (id)kCFNull) return nil;
if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
// 创建当前类的类对象实例
Class cls = [self class];
// 创建和获取 模型的元类(包含类的详细信息)
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
// 判断使用者是否自定义 类的(子类)类型 相当于判断这个类是否使用了modelCustomClassForDictionary 这个方法如果是则调用。
//这个方法定义在协议里了。使用方法相当于代理之前的判断。
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
// 创建实例实例
NSObject *one = [cls new];
// 为属性赋值 判断是否已经按照专门的方式处理过了。重点赋值处
if ([one yy_modelSetWithDictionary:dictionary]) return one;
return nil;
}
- _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]; 这是一个YYModelMeta对象的初始化 这个相当于作者自己自定义模仿了的一个元类。
@interface _YYModelMeta : NSObject {
@package
YYClassInfo *_classInfo;//类信息
//通过字典存储属性元类信息
NSDictionary *_mapper;
//存储所有的属性元类信息
NSArray *_allPropertyMetas;
//存储所有属性元类信息对应的映射key
NSArray *_keyPathPropertyMetas;
//存储需要映射到多个key的属性元类信息
NSArray *_multiKeysPropertyMetas;
//存储映射key的数目,该数字和_mapper.count相同
NSUInteger _keyMappedCount;
//model类的枚举类型
YYEncodingNSType _nsType;
BOOL _hasCustomWillTransformFromDictionary; //是否即将进行字典转化为model
BOOL _hasCustomTransformFromDictionary;//是否已经进行了字典转化为model
BOOL _hasCustomTransformToDictionary;//是否需要将model转化为字典
BOOL _hasCustomClassFromDictionary;//是否已经完成字典到类信息的转换
}
这里我们可以看到YYModelMeta中里面有一个YYClassInfo是他的属性,然后YYClassInfo里面捕获了当前类的方法属性和成员变量。YYClassInfo里面具体怎么做的这里暂时先不讨论。
+ (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);//设置信号量为1
});
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);//信号量减1,其他线程不可以进行操作
_YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
dispatch_semaphore_signal(lock);//完成缓存类元数据缓存信息获取,信号量+1,释放线程访问权限
if (!meta || meta->_classInfo.needUpdate) { //如果类元数据不存在或者需要更新
meta = [[_YYModelMeta alloc] initWithClass:cls];//直接初始化类元数据
if (meta) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);//获取锁使用权,信号量 -1
、 //存储初始化的类元数据信息到字典中
CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
dispatch_semaphore_signal(lock);//存储完成,释放锁权限
}
}
return meta;
}
这个类方法中采用了信号量来保证在完成获取缓存的类信息过程和存储类信息过程的线程安全。这里最重要的方法在类元数据的 meta = [[_YYModelMeta alloc] initWithClass:cls]; 接下来,我们看看它内部是如何实现类元数据的初始化的,以下代码不重要的地方有所省略。
- (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];
}
}
// 获取容器中存放的model类型映射,也是model中数组中存放的model类型映射
NSDictionary *genericMapper = nil;
if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
//获取需要数组中存放的model类以及对应的key值
genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];
if (genericMapper) {
NSMutableDictionary *tmp = [NSMutableDictionary new];
//当key是字符串类型,并且数组中存放的类是一个类或者是字符类型,则初始化为类,存起来
//这里主要是为了过滤乱写的一些映射关系
[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;
//递归映射属性元类信息,但忽略根类(NSObject/NSProxy)
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;
//这里通过属性信息,以及类信息,还有通过上面存放的数组内model和key关系
//判断当前属性model是否存放在数组里面,如果是,则取出对应的类,进行属性元信息的获取
_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];
/ //初始化存储所有属性元数据key的数组
NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
//初始化存放所有多个key需要对应同一个model的数组
NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
[customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
//根据替换前key获取对应的属性元数据类
_YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
if (!propertyMeta) return;
//因为需要替换key,所以需要删除原来的key
[allPropertyMetas removeObjectForKey:propertyName];
//当需要替换的key是NSString类型时,证明是需要改为一个key对应一个属性元数据类
if ([mappedToKey isKindOfClass:[NSString class]]) {
if (mappedToKey.length == 0) return;
//属性元数据类映射key替换为最新的key
propertyMeta->_mappedToKey = mappedToKey;
//用来处理替换key类型为 @"desc":@"ext.desc"类型的数据,ext.desc是ect类型model类型desc字段直接赋值给desc
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;
//存储一对一替换key属性元数据类到数组
[keyPathPropertyMetas addObject:propertyMeta];
}
/**检查是否有同样key对应多个属性,这里有一个小技巧,当mapper[mappedToKey]指向
当前最新那个propertyMeta,前一个mapper[mappedToKey]被记录到了当前propertyMeta->_next
里面去了,所以要读取到所有相同mappedToKey的propertyMeta是,只要mapper[mappedToKey],
mapper[mappedToKey]->_next,不停遍历,直到nil即可。
*/
propertyMeta->_next = mapper[mappedToKey] ?: nil;
mapper[mappedToKey] = propertyMeta;
} else if ([mappedToKey isKindOfClass:[NSArray class]]) {
//当一个属性对应多个不同json里面的key是,如@"sookID": @[@"id", @"ID", @"sook_id"]
NSMutableArray *mappedToKeyArray = [NSMutableArray new];
for (NSString *oneKey in ((NSArray *)mappedToKey)) {
if (![oneKey isKindOfClass:[NSString class]]) continue;
if (oneKey.length == 0) continue;
//这里也兼容@"desc":@"ext.desc"类型的字段替换
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;
//一个属性对应多个key的映射key全部存放于当前属性元数据类的_mappedToKeyArray数组中
propertyMeta->_mappedToKeyArray = mappedToKeyArray;
、 //存储一对多替换key属性元数据类到数组
[multiKeysPropertyMetas addObject:propertyMeta];
propertyMeta->_next = mapper[mappedToKey] ?: nil;
mapper[mappedToKey] = propertyMeta;
}
}];
}
/**因为前面替换key那里已经从allPropertyMetas删除了需要替换的key以及对应的value,这里存放的是不
需要替换key的属性元数据类映射字典,这里将他们也放到mapper中存放
*/
[allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
propertyMeta->_mappedToKey = name;
propertyMeta->_next = mapper[name] ?: nil;
mapper[name] = propertyMeta;
}];
//这里将获取当的所有映射关系的mapper存储为全局变量
if (mapper.count) _mapper = mapper;
//将一对一替换key属性元数据类存储到全局数组
if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas;
//一对多替换key属性元数据类存储到全局数组
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;
}
上面我们将类元数据类初始化方法做了简单的代码注释,通过注释可以了解到整个流程,
- 这个是modelCustomClassForDictionary的举例说明,可以用这个方法,利用字典中数据key的不同创建出不同类的模型。工厂模式吗?感觉应该用的比较少吧。
/// 模型对象的类元信息
@interface _YYModelMeta : NSObject {
@package
YYClassInfo *_classInfo;
/// Key:mapped key and key path, Value:_YYModelPropertyMeta. 数据结构:{"pic": [_YYModelPropertyMeta new]}
NSDictionary *_mapper;
/// Array<_YYModelPropertyMeta>, 所有有效属性元的数组
NSArray *_allPropertyMetas;
/// Array<_YYModelPropertyMeta>, 映射到键值路径的属性元
NSArray *_keyPathPropertyMetas;
/// Array<_YYModelPropertyMeta>, 映射到多个键的属性元
NSArray *_multiKeysPropertyMetas;
/// 有效的键值对数量,所谓有效即包含 _getter、_setter、成员变量。 值与 _mapper.count 相同
NSUInteger _keyMappedCount;
/// 数据类型
YYEncodingNSType _nsType;
BOOL _hasCustomWillTransformFromDictionary;
BOOL _hasCustomTransformFromDictionary;
BOOL _hasCustomTransformToDictionary;
BOOL _hasCustomClassFromDictionary;
}
@end
- 对这个类进行处理
这个_yymodelMeta 就在同一个文件中。可以理解为作者为了方便写在一起了。
//类对象信息初始化
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
classInfo 初始化
+ (instancetype)classInfoWithClass:(Class)cls {
if (!cls) return nil;
//定义缓存
static CFMutableDictionaryRef classCache;
static CFMutableDictionaryRef metaCache;
//dispatch_once(&onceToken,^{})确保了初始化过程只有一次单例
static dispatch_once_t onceToken;
//定义锁
static dispatch_semaphore_t lock;
dispatch_once(&onceToken, ^{
//使用CFDictionary创建了cache缓存
classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
//创建锁
lock = dispatch_semaphore_create(1);
});
//等待锁,当信号总量少于0的时候会一直等待,否则就可以正常执行,并让信号量-1
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
//初始化 classInfo,通过key获取value
YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
if (info && info->_needUpdate) {
[info _update];//类信息更新
}
//信号锁,信号总量+1
dispatch_semaphore_signal(lock);
if (!info) {
info = [[YYClassInfo alloc] initWithClass:cls];//初始化
if (info) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
//设置字典 key == bridge class value == _bridge info
CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
dispatch_semaphore_signal(lock);
}
}
return info;
}
就这样粗略记录了一下,成了一个性能有保障,安全性高的 YYClassInfo,然后 YYmodelMeta 再对其进行封装,配上不同的映射关系,巧妙的通过 runtime 处理一系列问题啦。
话说回来,我们一直要记住它做的一个核心功能就是取出 JOSN中对应的 value 设置 model 中属性设置值就 OK 了,这样就可以更好的理解。
这里实际上初始化过程跟咱们类元数据类的初始化方法实现一模一样。我们主要需要了解两个地方,第一个就是 [info _update] 类信息更新的实现,以及 info = [[YYClassInfo alloc] initWithClass:cls]; 类初始化实现过程。
- 创建了一些静态变量,创建一个1的信号量,用信号量来控制任务。
(1).创建了一个1个信号量,可以理解为停车场上还有1个空位。
(2).wait判断否是需要等待,如果信号量大于1就执行后面的代码,并且把信号量减1,任务执行完毕再执行signal,把信号量+1,每个任务都遵循这个规则,保证同时执行的任务数量不会大于信号量。
initWithClass
- (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;
}
通过这个方法,这里利用runtime方法获取当前class以及superClass,以及当前class的元类,还有类名等信息,关于父类对象的类信息则是采用递归调用的形式来进行初始化的。并且咱们从这里观察到类对象的初始化方法中也调用了[self _update]; 来更新类信息。
_update
了解[info _update]方法的实现过程
- (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);
}
//获取类的实例变量列表,并存储到全局字典中
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;
}
通过以上代码可知,update方法主要是对类对象的方法列表类和属性列表类以及实例变量列表类进行更新。
YYClassMethodInfo对方法列表的操作管理
补充:这里对method 做一个简单地补充
struct objc_method {
SEL method_name; // 选择子(方法名字)
char \*method_types; // 方法的参数列表
IMP method_imp; // 函数指针(方法实现)
}
id (*IMP)(id, SEL, ...)
对比一下其中常用到的一个方法,objc_msgSend(receiver, SEL op, ...);
/**
model: 谁执行
meta->_setter: 执行的方法
value: 传的值
*/
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
接下来我们对类方法列表实现进行讲解,首先我们了解一下方法列表类有哪些属性。
/**
Method information.
*/
@interface YYClassMethodInfo : NSObject
@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; // 方法参数编码类型
/**
Creates and returns a method info object.
@param method method opaque struct
@return A new object, or nil if an error occurs.
*/
- (instancetype)initWithMethod:(Method)method;
- (instancetype)initWithMethod:(Method)method {
if (!method) return nil;
self = [super init];
_method = method;
//获取SEL
_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;
}
整个方法实现非常简单,主要是获取方法imp函数指针,获取方法选择器sel,获取方法类型,以及返回值类型,参数等信息,然后存储到方法对象属性里面。
YYClassPropertyInfo属性列表
补充:首先注意“属性” (property)有两大概念:- ivar(实例变量)
- 存取方法(access method = getter + setter)。
@property = getter + setter;
struct property_t {
const char *name; // 名字
const char *attributes; // 属性的声明描述,例如 strong nonatomic之类的
};
陈宜龙为了搞清属性是怎么实现的,曾经反编译过相关的代码,他大致生成了五个东西
OBJC_IVAR_$类名$属性名称:该属性的“偏移量” (offset),这个偏移量是“硬编码” (hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。setter与getter方法对应的实现函数ivar_list:成员变量列表method_list:方法列表prop_list:属性列表
也就是说我们每次在增加一个属性,系统都会在 ivar_list中添加一个成员变量的描述,在method_list中增加 setter与 getter方法的描述,在prop_list中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出 setter 与 getter 方法对应的实现,在 setter 方法中从偏移量的位置开始赋值,在 getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转。
通过上述陈宜龙的解释,对 property 又有了很好的认识。
/**
Property information.
*/
@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 (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; // 属性相关协议
@property (nonatomic, assign, readonly) SEL getter; // getter方法选择器
@property (nonatomic, assign, readonly) SEL setter; // setter方法选择器
/**
Creates and returns a property info object.
@param property property opaque struct
@return A new object, or nil if an error occurs.
*/
- (instancetype)initWithProperty:(objc_property_t)property;
- (instancetype)initWithProperty:(objc_property_t)property {
if (!property) return nil;
self = [super init];
_property = property;
//获取属性名,并将其从C字符串转化为NSString类型
const char *name = property_getName(property);
if (name) {
_name = [NSString stringWithUTF8String:name];
}
YYEncodingType type = 0;
unsigned int attrCount;
// 获取属性对应的所有描述,即objc_property_attribute_t的结构体数组,包括原子性、数据类型、内存语义等
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
for (unsigned int i = 0; i < attrCount; i++) {
// objc_property_attribute_t的name为C字符串,name[0]表示获取第一个字符
switch (attrs[i].name[0]) {
case 'T': { // 这里将解析数据的类型,比如NSString、BOOL、NSArray等
if (attrs[i].value) {
_typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
// 用于解析数据类型
type = YYEncodingGetType(attrs[i].value);
//接下为NSObject对象类型,并且数据类型存在以及类型编码也存在时进行如下解析
if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
//用需要扫描的_typeEncoding字符串,初始化scanner对象
NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
// 我们的目的是拿到NSString,所以需要更改scanner扫描的起始位置为字符N所在的位置
if (![scanner scanString:@"@"" intoString:NULL]) continue;
/** 开始扫描_typeEncoding直到遇到字符”或者<,遇到了则返回YES,并将扫描到的字符串存入clsName。
需要注意的是NSCharacterSet其实设置的是停止扫描的字符,如果该字符出现在NSScanner扫描的起始位置,该方法会返回NO,clsName也会为null */
NSString *clsName = nil;
if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@""<"] intoString:&clsName]) {
if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
}
//当上面返回NO,进行协议的捕获
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': {
//获取自定义的getter方法
type |= YYEncodingTypePropertyCustomGetter;
if (attrs[i].value) {
_getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
}
} break;
case 'S': { //获取自定义的setter方法
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以及验证_setter、_getter是否已经获取到,如果没有自定义_setter或者_getter方法,就根据方法名生成_getter和_setter方法
_type = type;
if (_name.length) {
if (!_getter) {
_getter = NSSelectorFromString(_name);
}
if (!_setter) {
_setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
}
}
return self;
}
YYClassIvarInfo实例变量
/**
Instance variable information.
*/
@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; // 变量枚举类型
/**
Creates and returns an ivar info object.
@param ivar ivar opaque struct
@return A new object, or nil if an error occurs.
*/
- (instancetype)initWithIvar:(Ivar)ivar;
- (instancetype)initWithIvar:(Ivar)ivar {
if (!ivar) return nil;
self = [super init];
_ivar = ivar;
const char *name = ivar_getName(ivar);
if (name) {
_name = [NSString stringWithUTF8String:name];
}
_offset = ivar_getOffset(ivar);
const char *typeEncoding = ivar_getTypeEncoding(ivar);
if (typeEncoding) {
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
_type = YYEncodingGetType(typeEncoding);
}
return self;
}
- 小结论:这边可以大致了解了YYModel的结构了。YYModel主要在NSObject的拓展中实现,主要由YYModelMeta作为元类,来收集模型类的数据。YYModelMeta中主要由YYClassInfo,而YYclassInfo则是由YYClassIvarInfo、YYClassMethodInfo、YYClassPropertyInfo三部分组成。
YYClassInfo,最后,YYClassInfo 将上述综合起来,构成了一个很好操纵 ivar,method,property 的 Class。
@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
- (void)setNeedUpdate;
- (BOOL)needUpdate;
+ (nullable instancetype)classInfoWithClass:(Class)cls;
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;
@end
在YYModelMeta初始化方法中
- (instancetype)initWithClass:(Class)cls{
...
_YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
propertyInfo:propertyInfo
generic:genericMapper[propertyInfo.name]];
···
}
/// A property info in object model.
@interface _YYModelPropertyMeta : NSObject {
@package
NSString *_name; // 属性名
YYEncodingType _type; // 属性编码类型
YYEncodingNSType _nsType; // 属性的Foundation类型
BOOL _isCNumber; // 是否是C语言的Number类型
Class _cls; // 属性所属的class
Class _genericCls; // 属性为容器时比如NSDctionary、NSArray内部泛型类,存储的class类型,可能为nil
SEL _getter; // getter方法,实例变量不能响应时为nil
SEL _setter; // setter方法, 实例变量不能响应时为nil
BOOL _isKVCCompatible; // 是否可以通过键值编码KVC访问
BOOL _isStructAvailableForKeyedArchiver; //是否为可通过键值归档的结构体
BOOL _hasCustomClassFromDictionary; ///< 类或者泛型类实现了 +modelCustomClassForDictionary:方法,则为YES
/*
property->key: _mappedToKey:key _mappedToKeyPath:nil _mappedToKeyArray:nil
property->keyPath: _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
property->keys: _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath _mappedToKeyArray:keys(array)
*/
NSString *_mappedToKey; // 映射的key
NSArray *_mappedToKeyPath; // json中keyPath路劲映射到当前key,如ext.desc路径的值映射到desc属性
NSArray *_mappedToKeyArray; // json中多个key中第一个不为空的值映射到当前key,如果存在这种数组,则为nil
YYClassPropertyInfo *_info; // 属性信息
_YYModelPropertyMeta *_next; // 如果有多个属性映射到同一个 key 则指向下一个模型属性元
}
+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
// 支持伪协议,如果存在协议,并且有协议同名类存在,则设置generic为那个类
if (!generic && propertyInfo.protocols) {
for (NSString *protocol in propertyInfo.protocols) {
Class cls = objc_getClass(protocol.UTF8String);
if (cls) {
generic = cls;
break;
}
}
}
_YYModelPropertyMeta *meta = [self new];
meta->_name = propertyInfo.name;
meta->_type = propertyInfo.type;
meta->_info = propertyInfo;
meta->_genericCls = generic;
if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) {
/*这里实际上是判断是否为自定义的对象类型,如果为系统的则能检测出来,如果是自定义的,则
直接显示 YYEncodingTypeNSUnknown
*/
meta->_nsType = YYClassGetNSType(propertyInfo.cls);
} else {
meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type);
}
if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) {
/*
It seems that NSKeyedUnarchiver cannot decode NSValue except these structs:
*/
static NSSet *types = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableSet *set = [NSMutableSet new];
// 32 bit
[set addObject:@"{CGSize=ff}"];
[set addObject:@"{CGPoint=ff}"];
[set addObject:@"{CGRect={CGPoint=ff}{CGSize=ff}}"];
[set addObject:@"{CGAffineTransform=ffffff}"];
[set addObject:@"{UIEdgeInsets=ffff}"];
[set addObject:@"{UIOffset=ff}"];
// 64 bit
[set addObject:@"{CGSize=dd}"];
[set addObject:@"{CGPoint=dd}"];
[set addObject:@"{CGRect={CGPoint=dd}{CGSize=dd}}"];
[set addObject:@"{CGAffineTransform=dddddd}"];
[set addObject:@"{UIEdgeInsets=dddd}"];
[set addObject:@"{UIOffset=dd}"];
types = set;
});
if ([types containsObject:propertyInfo.typeEncoding]) {
meta->_isStructAvailableForKeyedArchiver = YES;
}
}
meta->_cls = propertyInfo.cls;
if (generic) {
meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)];
} else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) {
meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];
}
if (propertyInfo.getter) {
if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
meta->_getter = propertyInfo.getter;
}
}
if (propertyInfo.setter) {
if ([classInfo.cls instancesRespondToSelector:propertyInfo.setter]) {
meta->_setter = propertyInfo.setter;
}
}
if (meta->_getter && meta->_setter) {
/*
KVC invalid type:
long double
pointer (such as SEL/CoreFoundation object)
*/
switch (meta->_type & YYEncodingTypeMask) {
case YYEncodingTypeBool:
case YYEncodingTypeInt8:
case YYEncodingTypeUInt8:
case YYEncodingTypeInt16:
case YYEncodingTypeUInt16:
case YYEncodingTypeInt32:
case YYEncodingTypeUInt32:
case YYEncodingTypeInt64:
case YYEncodingTypeUInt64:
case YYEncodingTypeFloat:
case YYEncodingTypeDouble:
case YYEncodingTypeObject:
case YYEncodingTypeClass:
case YYEncodingTypeBlock:
case YYEncodingTypeStruct:
case YYEncodingTypeUnion: {
meta->_isKVCCompatible = YES;
} break;
default: break;
}
}
return meta;
}
- 小结论:可以理解为通过上述的操作已经将模型中所有数据都拿到了,全都存放到YYClassInfo中。
开始映射给模型赋值
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
......
NSObject *one = [cls new];
if ([one yy_modelSetWithDictionary:dictionary]) return one;
return nil;
}
然后调用 [one yy_modelSetWithDictionary:dictionary] 方法来进行YYClassInfo内容的赋值。
- (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;
//实现了,这个协议方法则,dic使用协议返回的内容
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);//需要映射的model
context.dictionary = (__bridge void *)(dic);//映射所需要的数据
//判断模型元键值映射数量与 JSON 所得字典的数量关系
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
/*
一般情况下他们的数量相等
特殊情况比如有的属性元会映射字典中的多个key
为字典中的每个键值对调用 ModelSetWithDictionaryFunction
这句话是核心代码,一般情况下就是靠 ModelSetWithDictionaryFunction
通过字典设置模型,它会递归调用将字典中所有key全部映射到model中
*/
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
//判断模型中是否存在映射keyPath的属性元
if (modelMeta->_keyPathPropertyMetas) {
//为每个映射keyPath的属性元执行 ModelSetWithPropertyMetaArrayFunction
CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
//判断模型中是否存在映射多个key的属性元
if (modelMeta->_multiKeysPropertyMetas) {
//为每个映射多个key的属性元执行 ModelSetWithPropertyMetaArrayFunction
CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
} else {
/*
模型元键值映射数量少,则认为不存在映射多个key的属性元
直接为modelMeta的每个属性元执行 ModelSetWithPropertyMetaArrayFunction
*/
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
/*
该接口用于当默认 JSON 转 Model 不适合模型对象时做额外的逻辑处理
我们也可以用这个接口来验证模型转换的结果
*/
if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
上面的设置上下文ModelSetContext,实际上是一个包含了类数据的元类对象,以及映射model,待转换的字典信息的结构体。
上面方法中比较重要的有两个函数,ModelSetWithDictionaryFunction() 以及 ModelSetWithPropertyMetaArrayFunction() 函数。接下来,我们分别对这两个函数进行单独的讲解。
ModelSetWithDictionaryFunction() 的实现原理
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)];
//以及待映射的model
__unsafe_unretained id model = (__bridge id)(context->model);
/*
这里遍历属性元类,直到propertyMeta->_next= nil
其主要原因是检查同一个key对应多个属性的情况
*/
while (propertyMeta) {
if (propertyMeta->_setter) {
//如果该属性元类的setter方法存在,则调用下面函数,内部通过objc_msgSend(id,SEL,...)去实现
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
}
propertyMeta = propertyMeta->_next;
};
}
这个函数 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);
if (!propertyMeta->_setter) return;
id value = nil;
//如何属性元类存在JSON中多key对应model同一个key,则获取第一个不为nil的内容赋值给model对应的key
if (propertyMeta->_mappedToKeyArray) {
value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
} else if (propertyMeta->_mappedToKeyPath) {
//如果需要对keypath路径下的值设置给model的key,那么就通过以下函数去获取对应路径值
value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
} else {
//如果不属于以上两种情况,则直接取值
value = [dictionary objectForKey:propertyMeta->_mappedToKey];
}
if (value) {
//如果存在值,则直接调用下面函数,内部通过objc_msgSend(id,SEL,...)去实现
__unsafe_unretained id model = (__bridge id)(context->model);
ModelSetValueForProperty(model, value, propertyMeta);
}
}
接下来我们讲解一下,ModelSetValueForProperty(); 函数中是如何调用runtime的 objc_msgSend(id,SEL,...) 函数来实现setter方法赋值的。
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta) {
//如果属性是一个 CNumber,即输入 int、uint……
if (meta->_isCNumber) {
//转化为NSNumber,然后调用ModelSetNumberToProperty()函数封装的赋值NSNumber的操作赋值
NSNumber *num = YYNSNumberCreateFromID(value);
ModelSetNumberToProperty(model, num, meta);
if (num != nil) [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) {
//// 如果属性属于 nsType,即 NSString、NSNumber……
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;
case YYEncodingTypeNSValue:
case YYEncodingTypeNSNumber:
case YYEncodingTypeNSDecimalNumber: {
if (meta->_nsType == YYEncodingTypeNSNumber) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSNumberCreateFromID(value));
} else if (meta->_nsType == YYEncodingTypeNSDecimalNumber) {
if ([value isKindOfClass:[NSDecimalNumber class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else if ([value isKindOfClass:[NSNumber class]]) {
NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum);
} else if ([value isKindOfClass:[NSString class]]) {
NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithString:value];
NSDecimal dec = decNum.decimalValue;
if (dec._length == 0 && dec._isNegative) {
decNum = nil; // NaN
}
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum);
}
} else { // YYEncodingTypeNSValue
if ([value isKindOfClass:[NSValue class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
}
}
} break;
case YYEncodingTypeNSData:
case YYEncodingTypeNSMutableData: {
if ([value isKindOfClass:[NSData class]]) {
if (meta->_nsType == YYEncodingTypeNSData) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
NSMutableData *data = ((NSData *)value).mutableCopy;
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
}
} else if ([value isKindOfClass:[NSString class]]) {
NSData *data = [(NSString *)value dataUsingEncoding:NSUTF8StringEncoding];
if (meta->_nsType == YYEncodingTypeNSMutableData) {
data = ((NSData *)data).mutableCopy;
}
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
}
} break;
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;
case YYEncodingTypeNSURL: {
if ([value isKindOfClass:[NSURL class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else if ([value isKindOfClass:[NSString class]]) {
NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSString *str = [value stringByTrimmingCharactersInSet:set];
if (str.length == 0) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, nil);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, [[NSURL alloc] initWithString:str]);
}
}
} break;
case YYEncodingTypeNSArray:
case YYEncodingTypeNSMutableArray: {
if (meta->_genericCls) {//对属性元的泛型判断
NSArray *valueArr = nil;
if ([value isKindOfClass:[NSArray class]]) valueArr = value;
else if ([value isKindOfClass:[NSSet class]]) valueArr = ((NSSet *)value).allObjects;
if (valueArr) {
NSMutableArray *objectArr = [NSMutableArray new];
for (id one in valueArr) {
//如果获取到的是一个泛型类对象,则直接加入数组
if ([one isKindOfClass:meta->_genericCls]) {
[objectArr addObject:one];
} else if ([one isKindOfClass:[NSDictionary class]]) {
/*
正常情况获取到的是字典,则直接拿到容器对应的类的实例对象进行映射
这里有意思的是这里又是调用当前前面的那个映射方法
*/
Class cls = meta->_genericCls;
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:one];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
NSObject *newOne = [cls new];
[newOne yy_modelSetWithDictionary:one];
if (newOne) [objectArr addObject:newOne];
}
}
/*
映射得到的结果,设置给setter,此方法对应类型
@property (nonatomic, copy) NSArray<ItemModel*> *items;的属性
*/
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, objectArr);
}
} else {
if ([value isKindOfClass:[NSArray class]]) {
if (meta->_nsType == YYEncodingTypeNSArray) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
((NSArray *)value).mutableCopy);
}
} else if ([value isKindOfClass:[NSSet class]]) {
if (meta->_nsType == YYEncodingTypeNSArray) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)value).allObjects);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
((NSSet *)value).allObjects.mutableCopy);
}
}
}
} break;
case YYEncodingTypeNSDictionary:
case YYEncodingTypeNSMutableDictionary: {
if ([value isKindOfClass:[NSDictionary class]]) {
if (meta->_genericCls) {
NSMutableDictionary *dic = [NSMutableDictionary new];
[((NSDictionary *)value) enumerateKeysAndObjectsUsingBlock:^(NSString *oneKey, id oneValue, BOOL *stop) { if ([oneValue isKindOfClass:[NSDictionary class]]) {
Class cls = meta->_genericCls;
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:oneValue];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
NSObject *newOne = [cls new];
[newOne yy_modelSetWithDictionary:(id)oneValue];
if (newOne) dic[oneKey] = newOne;
}
}];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, dic);
} else {
if (meta->_nsType == YYEncodingTypeNSDictionary) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
((NSDictionary *)value).mutableCopy);
}
}
}
} break;
case YYEncodingTypeNSSet:
case YYEncodingTypeNSMutableSet: {
NSSet *valueSet = nil;
if ([value isKindOfClass:[NSArray class]]) valueSet = [NSMutableSet setWithArray:value];
else if ([value isKindOfClass:[NSSet class]]) valueSet = ((NSSet *)value);
if (meta->_genericCls) {
NSMutableSet *set = [NSMutableSet new];
for (id one in valueSet) {
if ([one isKindOfClass:meta->_genericCls]) {
[set addObject:one];
} else if ([one isKindOfClass:[NSDictionary class]]) {
Class cls = meta->_genericCls;
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:one];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
NSObject *newOne = [cls new];
[newOne yy_modelSetWithDictionary:one];
if (newOne) [set addObject:newOne];
}
}
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, set);
} else {
if (meta->_nsType == YYEncodingTypeNSSet) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, valueSet);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
((NSSet *)valueSet).mutableCopy);
}
}
} // break; commented for code coverage in next line
default: break;
}
}
} else {
BOOL isNull = (value == (id)kCFNull);
switch (meta->_type & YYEncodingTypeMask) {
case YYEncodingTypeObject: {
Class cls = meta->_genericCls ?: meta->_cls;
if (isNull) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
} else if ([value isKindOfClass:cls] || !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 {
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:value] ?: cls;
}
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;
}
}
}
完成值的赋值。