YYModel 源码分析:字典转模型

本文拿一个简单的例子,看 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;
}
运行时相关,下篇继续

github repo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值