YYModel 源码分析:模型设计

915 阅读5分钟

前文介绍了 YYModel 怎么字典转模型,

YYModel 源码分析:字典转模型

概述

本文继续探讨,modelMeta,存放模型的属性、和字典的键对应关系的描述文件

结构:

主要使用四个模型:

  • @interface YYClassPropertyInfo : NSObject

对应模型的属性

  • @interface YYClassInfo : NSObject

对应模型的类

包含属性字典,NSDictionary<NSString *, YYClassIvarInfo *>

  • @interface _YYModelPropertyMeta : NSObject

对应模型的属性,及其处理

包含一个成员变量 YYClassPropertyInfo *_info,

把模型取得的属性 YYClassPropertyInfo 及其处理,封装在一起

  • @interface _YYModelMeta : NSObject

对应模型的类,及其处理

包含一个成员变量 YYClassInfo *_classInfo,

把模型取得的类信息 YYClassInfo 及其处理,封装在一起

模型,为什么这么设计

下面,通过两个例子来介绍

例子一, + (NSDictionary *)modelCustomPropertyMapper

模型如下:


@interface Author : NSObject
@property NSString *name;
@property NSString *birthday;
@end

@implementation Author
@end
    
@interface Book : NSObject
@property NSString *name;
@property NSUInteger pages;
@property Author *reporter;

@end

@implementation Book

+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"pages" : @"p",
             @"reporter" : @[@"author",@"writer"]};
}

@end

代码实现

_YYModelMeta : NSObject 的实现中

先标记
  • 对于 [ @"pages" : @"p" ],

通过下面的成员变量,维护一层映射关系

NSDictionary *_mapper;
  • 对于 [ @"reporter" : @[@"author",@"writer"],

通过下面的成员变量,持有记录

NSArray *_multiKeysPropertyMetas;

具体实现

- (instancetype)initWithClass:(Class)cls {

    // ...
    NSMutableDictionary *mapper = [NSMutableDictionary 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 删除 @"pages" 对应的
            // allPropertyMetas 删除 @"reporter" 对应的
            [allPropertyMetas removeObjectForKey:propertyName];
            
            if ([mappedToKey isKindOfClass:[NSString class]]) {
                // 处理这个 ,
                // [ @"pages" : @"p" ]
                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;
                // 修改
                // 到了这里,allPropertyMetas 中  @"pages" 对应的 propertyMeta
                // 变成了 allPropertyMetas 中  @"p" 对应的 propertyMeta
                mapper[mappedToKey] = propertyMeta;
                // if ([mappedToKey isKindOfClass:[NSString class]])
            } else if ([mappedToKey isKindOfClass:[NSArray class]]) {
                // 处理这个 ,
                // [@"reporter" : @[@"author",@"writer"]]
                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;
                // 记录属性信息
                // _mappedToKeyArray 变成了, @[@"author",@"writer"]
                propertyMeta->_mappedToKeyArray = mappedToKeyArray;
                // 持有属性
                [multiKeysPropertyMetas addObject:propertyMeta];
                
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
                // if ([mappedToKey isKindOfClass:[NSArray class]])
            }
        }]; // enumerateKeysAndObjectsUsingBlock
    } // if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)])


     // 异常情况: [ @"pages" : @"p" ]
     // 上面已经把异常情况属性的 _mappedToKey 记录了
     // 下面把正常情况属性的 _mappedToKey 记录一遍

    [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
        propertyMeta->_mappedToKey = name;
        propertyMeta->_next = mapper[name] ?: nil;
        mapper[name] = propertyMeta;
    }];
        
     // ...
     if (mapper.count) _mapper = mapper;
     // 处理这个 ,
     // [@"reporter" : @[@"author",@"writer"]]
     if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas;
     // ...
}

再处理

字典转模型中,

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {

    // ...
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        // [ @"pages" : @"p" ] ,走下面这句
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        // ...
        if (modelMeta->_multiKeysPropertyMetas) {
            //  [@"reporter" : @[@"author",@"writer"]],走下面这句
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } 
    
    // ...

}
  • 对于 [ @"pages" : @"p" ]
// _key 是 “p”
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
    ModelSetContext *context = _context;
    __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
    // propertyMeta->_name 是 “pages”
    // 通过维护的一层映射关系 _mapper,拿到准确的 propertyMeta
    __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;
    };
}
  • 对于 [ @"reporter" : @[@"author",@"writer"],

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;
    // 取值
    if (propertyMeta->_mappedToKeyArray) {
        value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
    } 
    // ...
    // 赋值
    if (value) {
        __unsafe_unretained id model = (__bridge id)(context->model);
        ModelSetValueForProperty(model, value, propertyMeta);
    }
}

多键取值, 拿到值,就完结


static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) {
    id value = nil;
    for (NSString *key in multiKeys) {
        if ([key isKindOfClass:[NSString class]]) {
            value = dic[key];
            // 获取到了,就 OK
            if (value) break;
        } 
        // ...
    }
    return value;
}

例子 2,+ (NSDictionary *)modelContainerPropertyGenericClass

模型如下:


@interface Character : NSObject
@property NSString *name;
@property NSString *birthday;
@end

@implementation Character

@end
    
@interface BookTwo : NSObject
@property NSString *name;
@property NSUInteger pages;
@property NSArray *character;

@end

@implementation BookTwo

+ (NSDictionary *)modelContainerPropertyGenericClass {
    return @{@"character" : [Character class] };
}

@end

代码实现

先标记
  • 对于
@{@"character" : [Character class] }

这次用到了 _YYModelPropertyMeta : NSObject

他下面的成员变量,记录了容器类信息

 Class _genericCls;    
  • 具体实现

同上面一样,先回到 _YYModelMeta 的初始化方法,

    // 拿到每一个属性的容器类信息
    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;
                // 通过这个方法 object_getClass,
                // 区分 obj 是类,
                // 还是对象
                Class meta = object_getClass(obj);
                if (!meta) return;
                if (class_isMetaClass(meta)) {
                    tmp[key] = obj;
                } 
                // ...
            }];
            genericMapper = tmp;
        }
    }
    
    // 初始化,所有的属性信息 _YYModelPropertyMeta
    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) {
            // 通过自定义的属性名,过滤掉 NSObject 的自带信息
            if (!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;
    }
    

进入 _YYModelPropertyMeta 的初始化方法中,


+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
    // ...
    _YYModelPropertyMeta *meta = [self new];
    // ...
    // 成员变量,记录了容器类信息
    meta->_genericCls = generic;
    // ...
}
再处理

字典转模型中,

再次进入熟悉的配方,


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);
    
    // ...
    // 赋值方法
    ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
    // ...
}

赋值方法中,处理容器类的数据解析

static void ModelSetValueForProperty(__unsafe_unretained id model,
                                     __unsafe_unretained id value,
                                     __unsafe_unretained _YYModelPropertyMeta *meta) {
      // ...
      switch (meta->_nsType) {
          // ...
                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];
                                }
                            }
                            ((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);
                            }
                        } 
                        // ...
                    }
                } break;
           // ...                        
      }                               
}

github repo