本文拿一个简单的例子,看 YYModel 字典转模型的源代码
有这么个模型
@interface Author : NSObject
@property NSString *name;
@property NSString *birthday;
@end
@interface Book : NSObject
@property NSString *name;
@property NSUInteger pages;
@property Author *author;
@end
创建的一般流程是这样的
Book * page = [Book new];
page.name = @"天书";
page.pages = 245;
Author * writer = [Author new];
writer.name = @"孙大圣";
writer.birthday = @"不晓得";
page.author = writer;
使用 YYModel 后,可以这样
NSString * path = [NSBundle.mainBundle pathForResource: @"one" ofType: @"json"];
NSString* jsonString = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
Book * page = [Book yy_modelWithJSON: jsonData];
YYModel 通过运行时,把我们手动的部分,自动化了
- 通过运行时的自省特性,拿到模型的属性,与字典的键做匹配
然后实现自动赋值
- 通过递归,将模型一级一级的数据,都安排好
看源码
对其源代码,有简化
YYModel 的方法实现,
是对 NSObject,加扩展
@interface NSObject (YYModel)
@end
拿到数据,吐出模型
+ (instancetype)yy_modelWithJSON:(id)json {
// 将拿到的数据,转化为字典
NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
// 将字典,转换为模型
return [self yy_modelWithDictionary:dic];
}
给字典,拿到模型的方法
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
// 检测数据合规
if (!dictionary || dictionary == (id)kCFNull) return nil;
if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
// 创建模型
Class cls = [self class];
NSObject *one = [cls new];
// 这时候,空的模型,创建好了,
// 下面就是填入数据,
// 填数据 OK,就返回模型
// 填数据不 OK, 就 nil 了
if ([one yy_modelSetWithDictionary:dictionary]) return one;
return nil;
}
其中,这一行
NSObject *one = [cls new];
对应
Book * page = [Book new];
小结,YYModel 并不神奇,做的事情,也就是创建模型,给模型的属性填数据
给模型填数据
- (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;
// 有等待填入数据的模型,
// 有数据源,字典
// 有模型的属性,和字典的键对应关系的描述文件 modelMeta
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
// 对字典的每一个键值,批量调用 ModelSetWithDictionaryFunction 方法
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
// ...
// 省略两个 if
} else {
// ...
}
return YES;
}
进入属性赋值方法 ModelSetWithDictionaryFunction
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
// 把上下文,包含的 2 个信息,给还原出来
// 待填入数据的模型、和属性对应关系
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);
//...
// 给模型填入数据
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
//...
}
进入具体赋值方法 ModelSetValueForProperty
- 如果是字符串,走
YYEncodingTypeNSString
, 通过objc_msgSend
给模型的属性赋值
对于整型的值,手段类似
- 如果是二级字典,走
YYEncodingTypeObject
, 创建模型,
调用 yy_modelSetWithDictionary
,上面的流程,重新来一遍
递归的调用出现了
// 下面的方法,有大幅简化,仅用于举例
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta) {
if (meta->_nsTypeX) {
switch (meta->_nsTypeX) {
case YYEncodingTypeNSString:{
if ([value isKindOfClass:[NSString class]]) {
// 字符串,走这里
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
}
} break;
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;
// 二级字典,走这里
one = [cls new];
[one yy_modelSetWithDictionary:value];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
}
} break;
default: break;
}
}
}
上面的这一句
[cls new];
对应开头部分
Author * writer = [Author new];
流程就走完了
代码接着看
给一个 json, 拿到字典,
这个 json ,可以是字符串、字典、二进制数据 NSData
这边都做了相应的检查,和处理
+ (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]]) {
// 下面是处理,二进制数据 NSData
jsonData = json;
}
if (jsonData) {
dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
}
return dic;
}
通过运行时,拿到属性信息
入口是,这一句
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
进入这个类 _YYModelMeta
初始化方法, 看起来吓人,
又是单例、锁、信号量
实际很简单
有用的代码,只有一行
meta = [[_YYModelMeta alloc] initWithClass:cls];
创建两个单例,缓存,和一把锁,实际是信号量
然后获取 meta
,
有缓存,取缓存
没缓存,去创建
通过信号量这把锁,保证缓存操作,是线程安全的
+ (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 = [[_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;
}