美文网首页
YYModel源码学习

YYModel源码学习

作者: 野码道人 | 来源:发表于2021-04-12 19:27 被阅读0次
  • 目录
    1、模型在软件开发中的作用
    2、诞生背景
    3、整体架构
    4、核心流程

模型在软件开发中的作用

后台下发的数据格式一般是JSON格式,我们可以方便的将其转换为字典,即便如此,在使用时候仍然存在一些问题,如key值直接写在代码中,不能一眼看清下发的数据结构,维护起来极其不方便,而且通过字典数组的方式获取数据一个不小心就会crash,最主要的是会花费很多时间在字段匹配上,这时候模型的作用就体现出来了

@interface Person : NSObject
// 接口文档的链接注释在这里
@property (nonatomic, copy) NSString *name;      // 名字
@property (nonatomic, assign) NSUInterger age;   // 年龄
...

@end

如上,接口下发的数据结构一目了然,直接访问属性方便、安全,而且可以直接定位到相关文档,也便于后续拓展,这就是抽象的力量

诞生背景

将后台下发的数据结构抽象成Model,给程序员带来极大的方便,但是随着业务的爆发,这种数据转模型的代码会急剧增加,而且各个业务接口的数据结构几乎都不相同,转换的代价会越来越大,并且难于维护,每次修改数据结构都要修改数据转模型部分的代码,这大大降低了敏捷分开的迭代的速度,这时候程序员迫切需要一种数据自动转模型的工具,于是YYModel诞生了

整体架构

一共有两个实现文件NSObject+YYModel和YYClassInfo:3个分类,6个类,如下

NSObject+YYModel YYClassInfo
NSObject (YYModel) YYClassIvarInfo
NSArray (YYModel) YYClassMethodInfo
NSDictionary (YYModel) YYClassPropertyInfo
_YYModelPropertyMeta YYClassInfo
_YYModelMeta

YYClassInfo是类对象的OC封装,内部聚合了Ivar ivar、Method method、objc_property_t property
如下:

/**
 Class information for a class.
 */
@interface YYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; ///< class object
@property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object
@property (nullable, nonatomic, assign, readonly) Class metaCls;  ///< class's meta class object
@property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class
@property (nonatomic, strong, readonly) NSString *name; ///< class name
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties

公有方法有四个如下:类信息更新状态存取方法,和两个初始化器

- (void)setNeedUpdate;
- (BOOL)needUpdate;
+ (nullable instancetype)classInfoWithClass:(Class)cls;
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;

NSObject+YYModel中定义了两个内部类_YYModelPropertyMeta、_YYModelMeta,分别表示模型中的属性和类信息,YYClassPropertyInfo 和YYClassInfo可以理解为单纯的数据结构,而_YYModelPropertyMeta、_YYModelMeta则是组合在一起的模型单元,分别扮演树枝节点和叶子节点角色

/// A property info in object model.
@interface _YYModelPropertyMeta : NSObject {
    @package
    NSString *_name;             ///< property's name
    YYEncodingType _type;        ///< property's type
    YYEncodingNSType _nsType;    ///< property's Foundation type
    BOOL _isCNumber;             ///< is c number type
    Class _cls;                  ///< property's class, or nil
    Class _genericCls;           ///< container's generic class, or nil if threr's no generic class
    SEL _getter;                 ///< getter, or nil if the instances cannot respond
    SEL _setter;                 ///< setter, or nil if the instances cannot respond
    BOOL _isKVCCompatible;       ///< YES if it can access with key-value coding
    BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver
    BOOL _hasCustomClassFromDictionary; ///< class/generic class implements +modelCustomClassForDictionary:
    
    /*
     property->key:       _mappedToKey:key     _mappedToKeyPath:nil            _mappedToKeyArray:nil
     property->keyPath:   _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
     property->keys:      _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath    _mappedToKeyArray:keys(array)
     */
    NSString *_mappedToKey;      ///< the key mapped to
    NSArray *_mappedToKeyPath;   ///< the key path mapped to (nil if the name is not key path)
    NSArray *_mappedToKeyArray;  ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys)
    YYClassPropertyInfo *_info;  ///< property's info
    _YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key.
}
@end

/// A class info in object model.
@interface _YYModelMeta : NSObject {
    @package
    YYClassInfo *_classInfo;
    /// Key:mapped key and key path, Value:_YYModelPropertyMeta.
    NSDictionary *_mapper;
    /// Array<_YYModelPropertyMeta>, all property meta of this model.
    NSArray *_allPropertyMetas;
    /// Array<_YYModelPropertyMeta>, property meta which is mapped to a key path.
    NSArray *_keyPathPropertyMetas;
    /// Array<_YYModelPropertyMeta>, property meta which is mapped to multi keys.
    NSArray *_multiKeysPropertyMetas;
    /// The number of mapped key (and key path), same to _mapper.count.
    NSUInteger _keyMappedCount;
    /// Model class type.
    YYEncodingNSType _nsType;
    
    BOOL _hasCustomWillTransformFromDictionary;
    BOOL _hasCustomTransformFromDictionary;
    BOOL _hasCustomTransformToDictionary;
    BOOL _hasCustomClassFromDictionary;
}
@end

头文件定义了一系列的公有方法,包含初始化器和工具函数,这里不再详述

核心流程

先调用_yy_dictionaryWithJSON把json转成字典,然后调用modelWithDictionary生成model

+ (instancetype)modelWithJSON:(id)json {
    NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
    return [self modelWithDictionary:dic];
}

下面这个函数用来构造类信息和参数校验

+ (instancetype)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];
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    
    NSObject *one = [cls new];
    if ([one modelSetWithDictionary:dictionary]) return one;
    return nil;
}

初始化模型上下文,遍历所有属性,进行赋值

- (BOOL)modelSetWithDictionary:(NSDictionary *)dic {
    if (!dic || dic == (id)kCFNull) return NO;
    if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    
    // 构造_YYModelMeta 模型
    _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;
    }
    
    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
        // ModelSetWithDictionaryFunction 通过字典设置模型
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        if (modelMeta->_keyPathPropertyMetas) {
            // 每个映射 keyPath 的属性元执行 ModelSetWithPropertyMetaArrayFunction
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        if (modelMeta->_multiKeysPropertyMetas) {
            // 每个映射多个 key 的属性元执行 ModelSetWithPropertyMetaArrayFunction
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } else { 
        // 每个 modelMeta 属性元执行 ModelSetWithPropertyMetaArrayFunction
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    
    return YES;
}

最终调用到ModelSetValueForProperty函数,实现单个属性的赋值,通过objc_msgSend函数直接调用属性的setter方法进行赋值

相关文章

  • YYModel源码学习

    YYModel源码阅读 1.Demo简要介绍: 只有2个实现文件,NSObject+YYModel 和 YYCla...

  • YYModel源码学习

    前言 可从5方面去分析YYModel。 – 文件结构:有哪些文件,每个文件大致的功能 – 类结构及关联:有哪些类,...

  • YYModel源码学习

    目录1、模型在软件开发中的作用2、诞生背景3、整体架构4、核心流程 模型在软件开发中的作用 后台下发的数据格式一般...

  • YYImage/YYModel/YYCache

    1.YYImage源码分析2.YYModel源码分析3.郑钦洪_:YYModel 源码历险记model属性赋值过程...

  • YYKit

    揭秘 YYModel 的魔法(上)揭秘 YYModel 的魔法(下)YYCache 设计思路YYCache 源码解...

  • iOS源码阅读系列

    iOS源码阅读系列 AFNetworking SDWebimage YYModel

  • YYWebImage 源码剖析:线程调度与缓存策略

    系列文章:YYCache 源码剖析:一览亮点YYModel 源码剖析:关注性能YYAsyncLayer 源码剖析:...

  • MJExtension(一)

    继续进行优秀开源框架的源码学习,这次打算学习一些常用的model解析的框架,比如YYModel,MJExtensi...

  • IOS笔记:撸一个 JSON->Model 之 YYMo

    YYModel 源码阅读笔记:首先调用 1. + (instancetype)modelWithJSON:(id)...

  • YYModel使用教程

    我最近在通读 YYModel 源码,在快速上手使用的时候,发现网上对 YYModel 的使用解释很不完善。哪怕是 ...

网友评论

      本文标题:YYModel源码学习

      本文链接:https://www.haomeiwen.com/subject/lqkrfltx.html