YYModel 学习(二)

接着上篇的学习,本篇以以下两个地方为着手点继续学习。

// JSON 如何转化为Model
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary
// YYModel 代理
@protocol YYModel <NSObject>

一、 JSON 转化为Model

通常我们在用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

但是以上三个方法最终都会要调用一下的方法:

+ (instancetype)yy_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];
    // 是否已经使用自定义NSDictionary 转化过来
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    // 刚好在这个地方确定 class 类型
    NSObject *one = [cls new];
    // 判断是否已经按专门方式处理过了,**核心重点处**
    if ([one yy_modelSetWithDictionary:dictionary]) return one;
    return nil;
}

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
    if (!dic || dic == (id)kCFNull) return NO;
    if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    
    // 注意此处用的是object_getClass(self)
    _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;
    }
    
    /** 三种类型
     void *modelMeta;  ///< _YYModelMeta
     void *model;      ///< id (self)
     void *dictionary; ///< NSDictionary (json)
     */
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);
    
    /**
     * 先了解下 CFArrayApplyFunction CFDictionaryApplyFunction
        目的:遍历Array数组元素,每一次传入一个函数中进行处理
        优点:遍历容器类能带来性能提升
        函数处理方式(CFArrayApplierFunction):ModelSetWithPropertyMetaArrayFunction
     */
    
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        
        /**先对字典中的Value 进行处理,通常情况下一般都通过
           ===> ModelSetWithDictionaryFunction
           ===> ModelSetValueForProperty
           ===>  ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
          然后可能就直接设置完了。
          */
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        /**下面是为了适配其他情况做的处理*、
         //对应类型 @{@"name":@"user.name"}
        if (modelMeta->_keyPathPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        // 对应类型 @{@"name":@[@"name",@"oldname",@"newname"]}
        if (modelMeta->_multiKeysPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } else {
        // 对象类型 @{@"name":@"name"}
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    // 有没有从NSDictionary 转过成有效的Model
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

先让要转化的 object 统一都转化成我们需要的 NSDictionary(这一步上面我没有贴出来),然后再转化为我们需要的 Model Object,当然里面涉及到诸多判断和转化,最核心还是对YYModelMeta的使用,然后再对其进行处理专门的ModelSetWithPropertyMetaArrayFunction函数处理。

PS:里面YYModelMeta对象里面的属性是通过YYModel 代理的设置来的

static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
    /**一个结构体,包含 _YYModelMeta ,id (self),NSDictionary (json) */
    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;
    // 判断三种基本的映射关系
    // 并且转化成字典中 value (YYValueForKeyPath这个方法的获取)
    if (propertyMeta->_mappedToKeyArray) {
        value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
    } else if (propertyMeta->_mappedToKeyPath) {
        value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
    } else {
        value = [dictionary objectForKey:propertyMeta->_mappedToKey];
    }
    
    // 如果取到了有效的值,那就转化为我们需要的 Model
    if (value) {
        __unsafe_unretained id model = (__bridge id)(context->model);
        // 核心
        ModelSetValueForProperty(model, value, propertyMeta);
    }
}

此时还还是仅仅去处有效的 value,还没有正式转化为 Model,这时ModelSetValueForProperty

/**  Set value to model with a property meta */
static void ModelSetValueForProperty(__unsafe_unretained id model,
                                     __unsafe_unretained id value,
                                     __unsafe_unretained _YYModelPropertyMeta *meta)

里面根据 meta的类型又进行了诸多判断,重点可以看

case YYEncodingTypeNSString:
case YYEncodingTypeNSArray:
case YYEncodingTypeNSDictionary:

当然此处是和可变的情况一起处理,就以字符串举例

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;

此处也充分体现了严谨之处啊,可变和不可变,还有就是nsType都处于处于同一种类型下,还分分别给像NSNumberNSURLNSAttributedString的情况处理。简单的说,明明你标明了NSString,却给你传了一个其他类型的,这边同样都做了处理。

经过上述转化后,然后再调用下 modelsetter方法转化成我们需要的啦。

/**
    model: 谁执行
    meta->_setter: 执行的方法
    value: 传的值
*/
 ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);

等着所有的属性都走一遍之后,到此基本的 JSON 转化为 Model 就结束啦。

二、 YYModel中的代理

@protocol, 其实也是我们Model中写的最多一块

//返回一个 Dict,将 Model 属性名对映射到 JSON 的 Key。
+ (NSDictionary *)modelCustomPropertyMapper;

// 返回容器类中的所需要存放的数据类型 (以 Class 或 Class Name 的形式)。
+ (NSDictionary *)modelContainerPropertyGenericClass;

// 如果实现了该方法,则处理过程中会忽略该列表内的所有属性
+ (NSArray *)modelPropertyBlacklist ;

// 如果实现了该方法,则处理过程中不会处理该列表外的属性。
+ (NSArray *)modelPropertyWhitelist ;

进行进一步探索,发现,原来@protocol中的实现都是在YYModelMeta中的实现的。

@implementation _YYModelMeta
- (instancetype)initWithClass:(Class)cls {
    YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
    if (!classInfo) return nil;
    self = [super init];

    // TO DO 处理,也是处理代理的地方

    return self;
}

重点看一下对modelCustomPropertyMapper的处理

// 用字典添加 所有属性的PropertyMeta对象
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;
        // 创建一个我们需要的meta对象
        _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;
    }
    // 遍历父类的Property [while 语句中下一个]
    curClassInfo = curClassInfo.superClassInfo;
}
// 判断是否为空之后的处理
if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy;

// 对三种不同的映射关系进行处理
/**
   NSString *_mappedToKey =====  映射类型 @{@"name":@"name"}
   NSArray *_mappedToKeyPath ==== 映射类型 @{@"name":@"user.name"}
   NSArray *_mappedToKeyArray ===== 映射类型 @[@"name",@"oldname",@"newname"]
 */
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) {
        // 拿 上面获取到的 propertyMeta 对象
        _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:@"."];
            // 此时表明 新的映射 是 mappedToKeyPath 规则
            if (keyPath.count > 1) {
                propertyMeta->_mappedToKeyPath = keyPath;
                [keyPathPropertyMetas addObject:propertyMeta];
            }
            propertyMeta->_next = mapper[mappedToKey] ?: nil;
            // 此时就是相当于 赋予 新的 规则,新的映射关系
            mapper[mappedToKey] = propertyMeta;
            
        } else if ([mappedToKey isKindOfClass:[NSArray class]]) {
            // 这个地方对应的就是 mappedToKeyArray
            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放到相应的数组中
            propertyMeta->_mappedToKeyArray = mappedToKeyArray;
            [multiKeysPropertyMetas addObject:propertyMeta];
            // 在多个属性映射一个 key 的时候使用
            propertyMeta->_next = mapper[mappedToKey] ?: nil;
            mapper[mappedToKey] = propertyMeta;
        }
    }];
}

/** 这个地方就是对上面 modelCustomPropertyMapper 中的查漏补缺,
    处理些没有自定义的属性,让它们属性的mappedKey等于属性名
*/
[allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
    propertyMeta->_mappedToKey = name;
    // 在多个属性映射一个 key 的时候使用
    propertyMeta->_next = mapper[name] ?: nil;
    mapper[name] = propertyMeta;
}];

到这里,我们一个我们需要的 propertyMeta就已经产生了,然后就可以配合着上面的 字典转 Model ,基本的一套流程就可以走啦。

了解到此,对里面的实现已经有了个粗略的思路,从上面的实现发现其中的映射关系是必须先要了解的,那就是属性名和json key的对应

映射表
@{ @"name" : @"name"} //_mappedTokey
@{ @"name" : [@"name",@"oldname",@"newname"]} //_mappedToKeyArray
@{ @"name" : @"use.name"} //_mappedToKeyPath
@{ 
      @"name" : @"name",
      @"fullName" : @"name", 
      @"username" : @"name"
} // _next

特别要注意的是_next,这种对第四种情况的处理。

感觉目前自己对YYModel中还是一个混乱的认识,毕竟 runtime 那块东东还没有去分析,宏观上也缺乏一个很好的认识,所以下一篇学习是必须的啦。

备注参考:
http://www.jianshu.com/p/9723761d02db

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容