【iOS】YYModel第三方库源码

前言

之前我们学习了SDWebImage的源码,这篇文章来学习一下YYModel第三方库的源码

YYModel的性能优化

缓存

Model JSON转换过程中需要许多类的元数据,YYModel通过CFDictionaryCreateMutable方法将这个数据存入缓存,需要使用的时候直接拿出,就不用重复解析类

类的元数据包括:

  1. 属性信息: • 属性的名称、类型、访问权限等。 • 属性是否可选、默认值等信息。 • 特定于JSON映射的信息,例如属性与JSON键之间的映射关系。

  2. 方法信息: • 类中定义的方法,特别是设置(setter)和获取(getter)方法,这些对于属性的读写至关重要。

  3. 类型信息: • 类型信息帮助转换过程中正确处理数据类型,例如将JSON中的字符串转换为日期对象或整数。

  4. 继承信息: • 类的继承结构,了解一个类是否继承自另一个类,这在处理多态或类继承时特别重要。

JSONModel中其实也有缓存,只不过实现方式是将缓存作为关联对象存入全局map

避免KVC

JSONModel中通过KVC将相应的JSON数据赋值给属性,但如果考虑性能的话,Getter和Setter方法的性能要优于KVC,YYModel就是使用Getter和Setter方法进行赋值,性能上优越很多

尽量用纯C函数、内联函数

YYModel的实现尽量使用了纯C函数、内联函数,这样可以避免ObjC的消息发送带来的开销。例如,YYModel在处理键值映射和类型转换时,会用到如YYClassIvarInfoYYClassMethodInfo这样的C结构体来存储有关ivar和方法的信息,然后通过纯C函数来操作这些结构体,避免了频繁的ObjC方法调用

减少遍历的循环次数

在JSON和Model转换前,Model的属性个数和JSON的键值对个数都是已知的,那么这个时候选择数量比较少的那一个遍历,就可以减少循环的次数,节省很多时间。

YYModel容器类做属性

在使用容器类做为属性时,我们必须调用方法指明容器内的属性是什么类,因为容器类如 NSArrayNSDictionary类型无关 的(即它们可以包含任何类型的对象)。如果不明确指定容器中元素的类型,YYModel 无法决定将 JSON 中的数据转换为何种类型的对象

使用该方法指明容器中元素的类型:

objectivec 复制代码
+ (NSDictionary *)modelContainerPropertyGenericClass {
​
}

在源码角度是这样实现的:

objectivec 复制代码
// 获取容器的元素对象,存储到genericMapper key为属性名 value为该容器类里面元素的类型
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;
            
            // 获取对象的类(元类)
            Class meta = object_getClass(obj);
            if (!meta) return;
            
            // 如果对象是类对象(元类),直接使用
            if (class_isMetaClass(meta)) {
                // key为属性名 value为该容器类里面元素的类型
                tmp[key] = obj;
            } 
            // 如果对象是字符串类型,尝试将其转换为类
            else if ([obj isKindOfClass:[NSString class]]) {
                Class cls = NSClassFromString(obj);
                if (cls) {
                    tmp[key] = cls;
                }
            }
        }];
        // 使用处理后的字典替换原始字典
        genericMapper = tmp;
    }
}

架构分析

YYModel第三方库中有这几个文件,可以看到数量很少,是一个十分轻量级的模型转换库

显而易见可以把这些文件分为两个模块:

  • YYClassInfo 主要将Runtime 层级的一些结构体封装到 NSObject 层级以便调用。

  • NSObject+YYModel主要负责处理转换的逻辑以及提供接口 这里面转换的逻辑基本上都是用到了YYClassInfo中封装的Runtime结构体

YYClassInfo剖析

可以看出来YYClassInfo的结构,正如刚刚所说他是底层Runtime结构体的封装,把这些结构体封装到NSObject层级可以方便调用,这样就简化了代码处理的逻辑。

我们用几个例子来直观地看一看YYModel对这些结构体的封装:

YYClassIvarInfo 看做是作者对 Runtimeobjc_ivar 结构体的封装,objc_ivarRuntime 中表示变量的结构体。

流程剖析

JSON统一成NSDictionary

YYModel的核心功能就是把JSON格式的数据转换为Model,接下来我们就看看它是如何把JSON转换为Model的,从yy_modelWithJSON方法开始,它将JSON转化为Model:

可以看到他的实现是先把JSON转换成字典,再把字典转换为Model。那么先看看怎么把JSON转换为字典:

将NSDictionary 转换为Model对象

这一步通过方法yy_modelWithDictionary完成,先概括一下这个方法中干了三件事:

  • 提取出Model类中的所有属性

  • 创建一个元数组,用来存放属性中的各种信息,这样等后面验证类型等操作时就可以直接根据元数组验证

  • 将JSON数据映射到Model对象中

提取Model信息

提取Model信息通过*YYModelMeta *modelMeta = [*YYModelMeta metaWithClass:cls]; 这行代码实现,要看懂这个方法,首先先看一下_YYModelMeta这个类

从属性的名字和注释中可以看出来,这个类中包含了属性的信息,还包含了属性与key之间的映射关系,因此我们可以明确字典中的每个key对应的value属性,因而可以正确将NSDictionary中的值映射到Model属性上。

接下来看metaWithClass的源码:

从源码中可以看到最后调用了一个initWithClass进行元数组的创建,我们接着看看initWithClass是如何实现的:

先纵览一下整个流程:

具体实现过程如下:

objectivec 复制代码
- (instancetype)initWithClass:(Class)cls {
    YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls]; //获取基础类信息(创建对象缓存类的运行时信息,包含属性列表、方法列表、父类信息等)
    if (!classInfo) return nil;
    self = [super init];
    
    // Get black list
    NSSet *blacklist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {
        NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist];
        if (properties) {
            blacklist = [NSSet setWithArray:properties];
        }
    }//黑名单处理。标记不用序列化的属性
    
    // Get white list
    NSSet *whitelist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {
        NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist];
        if (properties) {
            whitelist = [NSSet setWithArray:properties];
        }
    }//处理白名单,与黑名单互斥
    
    // Get container property's generic class
    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;
                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;
        }
    }
    
    // Create all property metas.
    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;//跳过白名单
            _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;
    
    // create mapper
    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) {
            _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:@"."];
                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;
                mapper[mappedToKey] = propertyMeta; //构建最终映射字典
                
            } else if ([mappedToKey isKindOfClass:[NSArray class]]) {
                
                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->_mappedToKeyArray = mappedToKeyArray;
                [multiKeysPropertyMetas addObject:propertyMeta];
                
                propertyMeta->_next = mapper[mappedToKey] ?: nil;//通过 _next 指针处理多属性映射到同 key 的情况
                mapper[mappedToKey] = propertyMeta;
            }
        }];
    }
    
    [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
        propertyMeta->_mappedToKey = name;
        propertyMeta->_next = mapper[name] ?: nil;
        mapper[name] = propertyMeta;
    }];
    
    if (mapper.count) _mapper = mapper;
    if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas;
    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;
}

使用NSDictionary的数据填充Model

我们在前面已经创建了_YYModelMeta(存放属性元数据的容器),在这之中我们完成了属性黑白名单的过滤,以及属性名和JSON中字段名的对应关系

接下来我们就可以使用Model 类创建出一个Model,并从JSON (NSDictionary)中取出对应的值,对Model对象进行填充 ,最后再将生成的model对象返回就完成了整个序列化过程,这个过程在yy_modelSetWithDictionary中:

objectivec 复制代码
- (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;
    
    if (modelMeta->_hasCustomWillTransformFromDictionary) {
        dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    }
    
    //构建context 上下文中包括modelMeta model的各种映射信息,model 要填充的model对象, dictionary 包含数据的字典
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);//context.modelMeta存放了属性元数据
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);//context.dictionary中存放了JSON数据
    
    //根据效率选择遍历方式
    //开始将dictionary数据填充到model上,这里最关键的就是ModelSetWithPropertyMetaArrayFunction方法。
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        if (modelMeta->_keyPathPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        if (modelMeta->_multiKeysPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } else {
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

在进行数据填充时用到了一个方法:

objectivec 复制代码
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);

它实际上是对dic,也就是当前JSON所对应的NSDictionary的所有元素,应用ModelSetWithDictionaryFunction方法,并在每次调用中将context传递进去

接下来再看一下ModelSetWithDictionaryFunction方法:

objectivec 复制代码
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
    ModelSetContext *context = _context;
    // 取出字典封装的JSON数据
    __unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
    __unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);
    // 取出meta属性,目的是为了得到键与setter方法等
    //_propertyMeta:指向 _YYModelPropertyMeta 的指针,这是一个封装了属性相关信息(如映射的 JSON 键名、键路径、访问器等)的结构体。重要的是存放了映射的JSON键名,这里是通过先前初始化_propertyMeta实现的
    if (!propertyMeta->_setter) return;
    id value = nil;
    
    //通过属性信息里面的key的映射关系拿到字典里面对应的value值
    if (propertyMeta->_mappedToKeyArray) {
        value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
    } else if (propertyMeta->_mappedToKeyPath) {
        value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
    } else {
        value = [dictionary objectForKey:propertyMeta->_mappedToKey];
    }
    
    if (value) {
        // 取出模型设置value
        __unsafe_unretained id model = (__bridge id)(context->model);
        ModelSetValueForProperty(model, value, propertyMeta);
    }
}

最后在ModelSetValueForProperty中使用消息发送objc_msgSend通过setter方法设置value

objectivec 复制代码
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);