【iOS】源码学习-YYModel源码学习

【iOS】源码学习-YYModel源码学习

前言

之前学习了YYModel的用法等,这里主要学习一下其源码。

【iOS】YYModel

YYModel源码分析

YYModel本质分为YYClassInfo和NSObject+YYModel两个模块。

  • YYClassInfo:主要将Runtime层级的一些结构封装到NSObject层以便调用。
  • NSObject+YYModel:主要负责提供方便调用的接口以及实现具体的模型转换逻辑。

YYClassInfo

YYClassInfo是对底层objc_ivar结构体的OC面向对象封装,因为苹果屏蔽了结构体内部字段,只能用Runtime函数取值。其核心作用是把Runtime原生的C语言底层类型包装成OC对象,简化反射调用,降低使用门槛。同时为JSON与模型互转提供元数据支撑。

YYClassInfo核心结构体

这里一一对比YYClassInfo和Runtime层级的一些结构体的关系:

  1. YYClassIvarInfo和objc_ivar
objc 复制代码
@interface YYClassIvarInfo : NSObject
@property (nonatomic, assign, readonly) Ivar ivar;              ///< ivar opaque struct // 变量的结构体
@property (nonatomic, strong, readonly) NSString *name;         ///< Ivar's name // 变量名称
@property (nonatomic, assign, readonly) ptrdiff_t offset;       ///< Ivar's offset // 变量的偏移量
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding // 变量的类型编码
@property (nonatomic, assign, readonly) YYEncodingType type;    ///< Ivar's type // 变量类型

/**
 Creates and returns an ivar info object.
 
 @param ivar ivar opaque struct
 @return A new object, or nil if an error occurs.
 */
- (instancetype)initWithIvar:(Ivar)ivar;
@end

对应的objc_ivar源码:

objc 复制代码
struct objc_ivar {
    char * _Nullable ivar_name OBJC2_UNAVAILABLE; // 变量名称
    char * _Nullable ivar_type OBJC2_UNAVAILABLE; // 变量类型
    int ivar_offset OBJC2_UNAVAILABLE; // 变量偏移量
#ifdef __LP64__ // 定义__LP64__表示正在构建64位目标
    int space OBJC2_UNAVAILABLE; // 变量空间
#endif
}

其中name和typeEncoding由静态const char*通过stringWithUTF8String方法转为不可变NSString,且属性只读,然后使用strong比copy性能更好。

name和typeEncoding使用strong比copy性能好的原因在于:

  • 数据天然不可变:ivar_getName返回的const char*指向Runtime内核静态常量内存,永不修改、不会释放。通过stringWithUTF8String生成的是不可变NSString。
  • 属性是readonly,外部无法赋值:整个Info对象初始化一次性赋值,外部只有读取权限,没有赋值入口。
  • strong仅引用计数+1,比copy发送copy消息、做类型判断等开销损耗更小。
    YYEncodingType枚举的意义:原始typeEncoding是一串编码字符串,每次判断类型都去解析字符串很繁琐,框架初始化时一次性解析、映射成枚举,后续JSON转模型、类型匹配时直接通过枚举判断,提升转换速度。
  1. YYClassMethodInfo和objc_method
objc 复制代码
@interface YYClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method;                  ///< method opaque struct // 方法
@property (nonatomic, strong, readonly) NSString *name;                 ///< method name // 方法名
@property (nonatomic, assign, readonly) SEL sel;                        ///< method's selector // SEL
@property (nonatomic, assign, readonly) IMP imp;                        ///< method's implementation // IMP
@property (nonatomic, strong, readonly) NSString *typeEncoding;         ///< method's parameter and return types // 方法参数和方法类型
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding;   ///< return value's type // 返回值类型编码
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *argumentTypeEncodings; ///< array of arguments' type // 参数类型编码

/**
 Creates and returns a method info object.
 
 @param method method opaque struct
 @return A new object, or nil if an error occurs.
 */
- (instancetype)initWithMethod:(Method)method;
@end

对应的objc_method源码:

objc 复制代码
struct objc_method {
    SEL _Nonnull method_name OBJC2_UNAVAILABLE; // 方法选择器
    char * _Nullable method_types OBJC2_UNAVAILABLE; // 方法类型编码字符串
    IMP _Nonnull method_imp OBJC2_UNAVAILABLE; // 方法实现函数指针
}

原生objc_method只给了一整串method_types,没有区分返回值、参数。而YYClassMethodInfo在init初始化时一次性解析拆分,后续使用不用重复切割字符串,提升转换速度。

Q:YYModel为什么提前缓存IMP?

A:OC常规消息发送流程为objc_msgSend->查cache缓存->查methodLists->父类递归查找。这样多层查找消耗,而YYClassMethodInfo初始化时提前取出IMP,赋值模型时直接函数指针调用IMP,可跳过整套消息查找流程,性能得到巨大提升。

  1. YYClassPropertyInfo和property_t
objc 复制代码
@interface YYClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_t property; ///< property's opaque struct // 属性
@property (nonatomic, strong, readonly) NSString *name;           ///< property's name // 属性名称
@property (nonatomic, assign, readonly) YYEncodingType type;      ///< property's type // 属性类型
@property (nonatomic, strong, readonly) NSString *typeEncoding;   ///< property's encoding value // 属性类型编码
@property (nonatomic, strong, readonly) NSString *ivarName;       ///< property's ivar name // 变量名称
@property (nullable, nonatomic, assign, readonly) Class cls;      ///< may be nil // 类型
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; ///< may nil // 属性相关协议
@property (nonatomic, assign, readonly) SEL getter;               ///< getter (nonnull) / getter方法选择器
@property (nonatomic, assign, readonly) SEL setter;               ///< setter (nonnull) // setter方法选择器

/**
 Creates and returns a property info object.
 
 @param property property opaque struct
 @return A new object, or nil if an error occurs.
 */
- (instancetype)initWithProperty:(objc_property_t)property;
@end

对应的property_t源码:

objc 复制代码
struct property_t {
    const char *name; // 名称
    const char *attributes; // 修饰,拼接字符串,包含T类型、V对应ivar名、G自定义getter、S自定义setter、&/C/N内存修饰符等标记
};

我们发现原生property_t结构体没有自带getter、setter、ivar、class、协议等,全部需要手动解析attributes字符串来拿到。而YYClassPropertyInfo就实现了完整解析封装。初始化时拆解attributes标记,优先读取G/S自定义getter和setter方法,没有就按照规则拼接标准setter和getter方法,同时解析出ivar名称、类型class、协议、完整类型编码等。

  1. YYClassInfo和objc_class
objc 复制代码
@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 // 属性信息

/**
 If the class is changed (for example: you add a method to this class with
 'class_addMethod()'), you should call this method to refresh the class info cache.
 
 After called this method, `needUpdate` will returns `YES`, and you should call 
 'classInfoWithClass' or 'classInfoWithClassName' to get the updated class info.
 */
// 标记当前缓存的元数据已经过期,需要刷新
- (void)setNeedUpdate;

/**
 If this method returns `YES`, you should stop using this instance and call
 `classInfoWithClass` or `classInfoWithClassName` to get the updated class info.
 
 @return Whether this class info need update.
 */
// 读取脏标记,判断当前缓存是否失败
- (BOOL)needUpdate;

/**
 Get the class info of a specified Class.
 
 @discussion This method will cache the class info and super-class info
 at the first access to the Class. This method is thread-safe.
 
 @param cls A class.
 @return A class info, or nil if an error occurs.
 */
// 根据class获取、创建并缓存YYClassInfo实例,全局唯一缓存入口
+ (nullable instancetype)classInfoWithClass:(Class)cls;

/**
 Get the class info of a specified Class.
 
 @discussion This method will cache the class info and super-class info
 at the first access to the Class. This method is thread-safe.
 
 @param className A class name.
 @return A class info, or nil if an error occurs.
 */
// 根据类名字符串反向拿到class,再调classInfoWithClass
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;

@end

对应的objc_class源码:

objc 复制代码
typedef struct objc_class *Class;

// runtime.h
struct objc_class {
    Class _Nonnull isa OBJC_ISA_AVAILABILITY; // isa 指针

#if !__OBJC2__
    Class _Nullable super_class OBJC2_UNAVAILABLE; // 父类指针
    const char * _Nonnull name OBJC2_UNAVAILABLE; // 类名
    long version OBJC2_UNAVAILABLE; // 版本
    long info OBJC2_UNAVAILABLE; // 信息
    long instance_size OBJC2_UNAVAILABLE; // 初始尺寸
    struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; // 变量列表
    struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; // 方法列表
    struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; // 缓存
    struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; // 协议列表
#endif

} OBJC2_UNAVAILABLE;

原生objc_class底层是单向链表objc_ivar_list、

objc_method_list,查找某个属性或者变量需要从头遍历,速度慢。而YYClassInfo初始化时一次性遍历全部,存入字典,按键名秒查,使得YYModel性能大大提升。

YYClassInfo初始化

objc 复制代码
+ (instancetype)classInfoWithClass:(Class)cls {
    if (!cls) return nil;
    // 单例缓存classCache,对应缓存类
    static CFMutableDictionaryRef classCache;
    // 单例缓存metaCache,对应元类
    static CFMutableDictionaryRef metaCache;
    static dispatch_once_t onceToken;
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{
        classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    // 使用dispatch_semaphore加锁,保证单例缓存的线程安全
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    // 初始化前先根据当前YYClassInfo是否为元类去对应的单例缓存中查找
    YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
    // 如果找到且找到的信息需要更新则执行更新操作
    if (info && info->_needUpdate) {
        [info _update];
    }
    dispatch_semaphore_signal(lock);
    // 没找到就初始化
    if (!info) {
        info = [[YYClassInfo alloc] initWithClass:cls];
        if (info) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            // 根据初始化信息选择向对应的类、元类缓存注入信息
            // 字典中key = cls,value = info
            CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
            dispatch_semaphore_signal(lock);
        }
    }
    return info;
}

整个初始化步骤就是:

  1. 创建单例缓存、类缓存和元类缓存
  2. 使用dispatch_semaphore作为锁保证缓存线程安全
  3. 先去缓存查找是否已经向缓存中注册了当前要初始化的YYClassInfo。如果找到,则判断缓存对象是否需要更新并执行相关操作;反之未找到就执行初始化。
  4. 初始化完成后向缓存中注册YYClassInfo实例。

NSObject+YYModel

NSObject+YYModel是利用YYClassInfo层级封装好的类执行JSON、模型的一个转换逻辑,且提供给类一个无侵入性的接口。主要分为四个模块:

  • 类型编码解析
  • 数据结构定义
  • 递归模型转换
  • 接口相关代码

类型编码解析

类型编码解析主要通过YYEncodingNSType枚举来细分Foundation系统对象、YYClassGetNSType函数来将NS类型转为YYEncodingNSType、YYEncodingTypeIsCNumber函数来判断类型是否可以直接转为C语言数值类型的函数。

objc 复制代码
typedef NS_ENUM (NSUInteger, YYEncodingNSType) {
    YYEncodingTypeNSUnknown = 0,
    YYEncodingTypeNSString,
    YYEncodingTypeNSMutableString,
    YYEncodingTypeNSValue,
    YYEncodingTypeNSNumber,
    YYEncodingTypeNSDecimalNumber,
    YYEncodingTypeNSData,
    YYEncodingTypeNSMutableData,
    YYEncodingTypeNSDate,
    YYEncodingTypeNSURL,
    YYEncodingTypeNSArray,
    YYEncodingTypeNSMutableArray,
    YYEncodingTypeNSDictionary,
    YYEncodingTypeNSMutableDictionary,
    YYEncodingTypeNSSet,
    YYEncodingTypeNSMutableSet,
};

static force_inline YYEncodingNSType YYClassGetNSType(Class cls) {
    if (!cls) return YYEncodingTypeNSUnknown;
    if ([cls isSubclassOfClass:[NSMutableString class]]) return YYEncodingTypeNSMutableString;
    if ([cls isSubclassOfClass:[NSString class]]) return YYEncodingTypeNSString;
    if ([cls isSubclassOfClass:[NSDecimalNumber class]]) return YYEncodingTypeNSDecimalNumber;
    if ([cls isSubclassOfClass:[NSNumber class]]) return YYEncodingTypeNSNumber;
    if ([cls isSubclassOfClass:[NSValue class]]) return YYEncodingTypeNSValue;
    if ([cls isSubclassOfClass:[NSMutableData class]]) return YYEncodingTypeNSMutableData;
    if ([cls isSubclassOfClass:[NSData class]]) return YYEncodingTypeNSData;
    if ([cls isSubclassOfClass:[NSDate class]]) return YYEncodingTypeNSDate;
    if ([cls isSubclassOfClass:[NSURL class]]) return YYEncodingTypeNSURL;
    if ([cls isSubclassOfClass:[NSMutableArray class]]) return YYEncodingTypeNSMutableArray;
    if ([cls isSubclassOfClass:[NSArray class]]) return YYEncodingTypeNSArray;
    if ([cls isSubclassOfClass:[NSMutableDictionary class]]) return YYEncodingTypeNSMutableDictionary;
    if ([cls isSubclassOfClass:[NSDictionary class]]) return YYEncodingTypeNSDictionary;
    if ([cls isSubclassOfClass:[NSMutableSet class]]) return YYEncodingTypeNSMutableSet;
    if ([cls isSubclassOfClass:[NSSet class]]) return YYEncodingTypeNSSet;
    return YYEncodingTypeNSUnknown;
}

YYClassGetNSType函数逻辑:自上而下判断继承关系。先判断可变子类,再判断父类,然后返回对应枚举;都不匹配返回YYEncodingTypeNSUnknown。
force_inline内联函数:

相比于调用普通函数需要开辟栈帧、保存返回地址、参数压栈、跳转指令等开销损耗,static force_inline在编译时把函数代码原地展开嵌入调用处,不生成独立函数调用指令,直接抹平了这部分开销。

解析属性类型编码时,如果是block类型,需要做特殊过滤。

objc 复制代码
static force_inline Class YYNSBlockClass() {
    static Class cls;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        void (^block)(void) = ^{}; // 创建一个空的Block
        cls = ((NSObject *)block).class; // 获得block运行时真实类
      	// 循环向上查找父类,直到父类是NSObject,上一层就是统一NSBlock基类
        while (class_getSuperclass(cls) != [NSObject class]) { 
            cls = class_getSuperclass(cls);
        }
    });
    return cls; // current is "NSBlock"
}

三种运行时block子类,父链一致:

栈、堆、全局block运行时类名不一样,直接对比会判断失败。而它们拥有一个公共父类__NSBlock,因此拿这个基类统一判断是否为block类型

数据结构定义

NSObject+YYModel中重新定义了两个类,主要通过这两个类来使用YYClassInfo中的封装。

  • _YYModelPropertyMeta

单个属性的映射元数据,是一条属性完整的映射配置,即把YYClassPropertyInfo信息再加工、补充转换规则。

objc 复制代码
@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 // 判断是否为CNUmber基本类型
    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 // getter方法
    SEL _setter;                 ///< setter, or nil if the instances cannot respond // setter方法
    BOOL _isKVCCompatible;       ///< YES if it can access with key-value coding // 如果可以使用KVC就返回YES
    BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver // 如果可以使用archiver/unarchiver归解档就返回YES
    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 // 映射key
    NSArray *_mappedToKeyPath;   ///< the key path mapped to (nil if the name is not key path) // 映射keyPath
    NSArray *_mappedToKeyArray;  ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys) // key或者keyPath数组
    YYClassPropertyInfo *_info;  ///< property's info // 属性信息
    _YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key. // 如果有多个属性映射到同一个key,则执行下一个模型属性元
}
@end
  • _YYModelMeta

整个模型类的总元数据,是一个模型类全局转换配置,其持有全部属性meta数组、全局映射规则、黑白名单等。

objc 复制代码
@interface _YYModelMeta : NSObject {
    @package
    YYClassInfo *_classInfo;
    /// Key:mapped key and key path, Value:_YYModelPropertyMeta.
    NSDictionary *_mapper; // 核心哈希索引表。key:JSON字段名/多级keyPath value:对应这条字段的_YYModelPropertyMeta
    /// Array<_YYModelPropertyMeta>, all property meta of this model.
    NSArray *_allPropertyMetas; // 存放当前类所有属性对应的_YYModelPropertyMeta数组,包含自身和递归合并后的父类的全部属性
    /// Array<_YYModelPropertyMeta>, property meta which is mapped to a key path.
    NSArray *_keyPathPropertyMetas; // 被映射到keyPath的_YYModelPropertyMeta数组
    /// Array<_YYModelPropertyMeta>, property meta which is mapped to multi keys.
    NSArray *_multiKeysPropertyMetas; // 被映射到key的_YYModelPropertyMeta数组
    /// The number of mapped key (and key path), same to _mapper.count.
    NSUInteger _keyMappedCount; // key与keyPath的数量
    /// Model class type.
    YYEncodingNSType _nsType; // class的类型
    
    // 检测模型是否重写自定义转换代理方法
    BOOL _hasCustomWillTransformFromDictionary; // 是否实现modelWillTransformFromDictionary转换前回调
    BOOL _hasCustomTransformFromDictionary; // 是否实现modelTransformFromDictionary字典转模型自定义值加工
    BOOL _hasCustomTransformToDictionary; // 是否实现modelTransformToDictionary模型转字典自定义加工
    BOOL _hasCustomClassFromDictionary; // 是否实现modelClassForDictionary多态场景,根据字典内容动态切换模型子类Class
}
@end

字典模型转换底层实现

这里来看一下核心的YYModel内存中字典与模型双向转换的完整底层实现。

JSON->Model

objc 复制代码
+ (instancetype)yy_modelWithJSON:(id)json {
    NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
    return [self yy_modelWithDictionary:dic];
}

JSON转换为Model主要分为两步:

  • JSON->NSDictionary
objc 复制代码
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
    // kCFNull:CoreFoundation中CFNull的单例对象
    // CFNull是用来表示集合对象中的空值,其对象不允许被创建也不允许被销毁
    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]]) {
        // 如果是NSString类则用UTF-8编码转换为NSData
        jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
    } else if ([json isKindOfClass:[NSData class]]) {
        jsonData = json;
    }
    if (jsonData) {
        // 如果是jsonData则用NSJSONSerialization方法将jsonData转换为dic
        dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
        if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
    }
    return dic;
}

这里全面区分一下iOS五大空值:

  • NSDictionary->Model
objc 复制代码
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
    if (!dictionary || dictionary == (id)kCFNull) return nil;
    if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
    
    Class cls = [self class];
    // 使用当前类生成一个_YYModelMeta模型元类
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
    // _hasCustomClassFromDictionary:用于标识是否需要自定义返回类
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    
    NSObject *one = [cls new];
    // 为新建类实例赋值
    if ([one yy_modelSetWithDictionary:dictionary]) return one;
    return nil;
}

然后就进入到了模型转换的核心部分yy_modelSetWithDictionary,主要分为以下几个步骤:

  • 入参检验、初始化模型元以及映射表检验
  • 初始化模型设置context
  • 为字典中的每个键值对调用ModelSetWithDictionaryFunction
  • 检验转换结果
objc 复制代码
- (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)];
    // 如果没有key就返回NO
    if (modelMeta->_keyMappedCount == 0) return NO;
    
    // 是否有modelCustomWillTransformFromDictionary接口
    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); // 待转换的字典
    
    // 判断模型元键值映射数量与JSON所得字典的数量关系
    // 一般情况下两数量相同,特殊情况下有的属性可能会映射字典中多个key
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        // 核心:对每个键值对调用ModelSetWithDictionaryFunction,将dictionary数据填充到model层上
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        // 处理多个keyPath属性元
        if (modelMeta->_keyPathPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        // 处理多个key的属性元
        if (modelMeta->_multiKeysPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } else {
        // 处理单个key
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

其中调用的核心方法ModelSetWithDictionaryFunction:

objc 复制代码
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)];
    // 获得对应的一个未处理model
    __unsafe_unretained id model = (__bridge id)(context->model);
    // 遍历propertyMeta直到propertyMeta->next == nil
    while (propertyMeta) {
        // 找到setter不为空的属性元通过ModelSetValueForProperty方法赋值
        if (propertyMeta->_setter) {
            ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    };
}

ModelSetValueForProperty方法:

主要通过判断属性类型对model层的数据进行赋值。

  • 如果属性元是CNumber类型(如int、unit):使用ModelSetNumberToProperty赋值
  • 如果属性元是NSType类型(如NSString、NSNumber):根据类型转换中可能涉及的对应类型做逻辑判断并赋值
  • 如果属性元既不是CNumber也不是NSType(可能为id、Class、SEL、Block、struct、union、charn、void或char类型):猜测类型并做出相应转换和赋值
objc 复制代码
static void ModelSetValueForProperty(__unsafe_unretained id model,
                                     __unsafe_unretained id value,
                                     __unsafe_unretained _YYModelPropertyMeta *meta) {
    if (meta->_isCNumber) {
        NSNumber *num = YYNSNumberCreateFromID(value);
        ModelSetNumberToProperty(model, num, meta);
        if (num) [num class]; // hold the number
    } else if (meta->_nsType) {
        if (value == (id)kCFNull) {
            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
        } else {
            switch (meta->_nsType) {
                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;
                    
                case YYEncodingTypeNSValue:
                case YYEncodingTypeNSNumber:
                case YYEncodingTypeNSDecimalNumber: {
                    if (meta->_nsType == YYEncodingTypeNSNumber) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSNumberCreateFromID(value));
                    } else if (meta->_nsType == YYEncodingTypeNSDecimalNumber) {
                        if ([value isKindOfClass:[NSDecimalNumber class]]) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                        } else if ([value isKindOfClass:[NSNumber class]]) {
                            NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum);
                        } else if ([value isKindOfClass:[NSString class]]) {
                            NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithString:value];
                            NSDecimal dec = decNum.decimalValue;
                            if (dec._length == 0 && dec._isNegative) {
                                decNum = nil; // NaN
                            }
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum);
                        }
                    } else { // YYEncodingTypeNSValue
                        if ([value isKindOfClass:[NSValue class]]) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                        }
                    }
                } break;
                    
                case YYEncodingTypeNSData:
                case YYEncodingTypeNSMutableData: {
                    if ([value isKindOfClass:[NSData class]]) {
                        if (meta->_nsType == YYEncodingTypeNSData) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                        } else {
                            NSMutableData *data = ((NSData *)value).mutableCopy;
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
                        }
                    } else if ([value isKindOfClass:[NSString class]]) {
                        NSData *data = [(NSString *)value dataUsingEncoding:NSUTF8StringEncoding];
                        if (meta->_nsType == YYEncodingTypeNSMutableData) {
                            data = ((NSData *)data).mutableCopy;
                        }
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
                    }
                } break;
                    
                case YYEncodingTypeNSDate: {
                    if ([value isKindOfClass:[NSDate class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                    } else if ([value isKindOfClass:[NSString class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSDateFromString(value));
                    }
                } break;
                    
                case YYEncodingTypeNSURL: {
                    if ([value isKindOfClass:[NSURL class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                    } else if ([value isKindOfClass:[NSString class]]) {
                        NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
                        NSString *str = [value stringByTrimmingCharactersInSet:set];
                        if (str.length == 0) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, nil);
                        } else {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, [[NSURL alloc] initWithString:str]);
                        }
                    }
                } break;
                    
                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);
                            }
                        } else if ([value isKindOfClass:[NSSet class]]) {
                            if (meta->_nsType == YYEncodingTypeNSArray) {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)value).allObjects);
                            } else {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                               meta->_setter,
                                                                               ((NSSet *)value).allObjects.mutableCopy);
                            }
                        }
                    }
                } break;
                    
                case YYEncodingTypeNSDictionary:
                case YYEncodingTypeNSMutableDictionary: {
                    if ([value isKindOfClass:[NSDictionary class]]) {
                        if (meta->_genericCls) {
                            NSMutableDictionary *dic = [NSMutableDictionary new];
                            [((NSDictionary *)value) enumerateKeysAndObjectsUsingBlock:^(NSString *oneKey, id oneValue, BOOL *stop) {
                                if ([oneValue isKindOfClass:[NSDictionary class]]) {
                                    Class cls = meta->_genericCls;
                                    if (meta->_hasCustomClassFromDictionary) {
                                        cls = [cls modelCustomClassForDictionary:oneValue];
                                        if (!cls) cls = meta->_genericCls; // for xcode code coverage
                                    }
                                    NSObject *newOne = [cls new];
                                    [newOne yy_modelSetWithDictionary:(id)oneValue];
                                    if (newOne) dic[oneKey] = newOne;
                                }
                            }];
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, dic);
                        } else {
                            if (meta->_nsType == YYEncodingTypeNSDictionary) {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                            } else {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                               meta->_setter,
                                                                               ((NSDictionary *)value).mutableCopy);
                            }
                        }
                    }
                } break;
                    
                case YYEncodingTypeNSSet:
                case YYEncodingTypeNSMutableSet: {
                    NSSet *valueSet = nil;
                    if ([value isKindOfClass:[NSArray class]]) valueSet = [NSMutableSet setWithArray:value];
                    else if ([value isKindOfClass:[NSSet class]]) valueSet = ((NSSet *)value);
                    
                    if (meta->_genericCls) {
                        NSMutableSet *set = [NSMutableSet new];
                        for (id one in valueSet) {
                            if ([one isKindOfClass:meta->_genericCls]) {
                                [set 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) [set addObject:newOne];
                            }
                        }
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, set);
                    } else {
                        if (meta->_nsType == YYEncodingTypeNSSet) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, valueSet);
                        } else {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                           meta->_setter,
                                                                           ((NSSet *)valueSet).mutableCopy);
                        }
                    }
                } // break; commented for code coverage in next line
                    
                default: break;
            }
        }
    } else {
        BOOL isNull = (value == (id)kCFNull);
        switch (meta->_type & YYEncodingTypeMask) {
            case YYEncodingTypeObject: {
                if (isNull) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
                } else if ([value isKindOfClass:meta->_cls] || !meta->_cls) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value);
                } else if ([value isKindOfClass:[NSDictionary class]]) {
                    NSObject *one = nil;
                    if (meta->_getter) {
                        one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
                    }
                    if (one) {
                        [one yy_modelSetWithDictionary:value];
                    } else {
                        Class cls = meta->_cls;
                        if (meta->_hasCustomClassFromDictionary) {
                            cls = [cls modelCustomClassForDictionary:value];
                            if (!cls) cls = meta->_genericCls; // for xcode code coverage
                        }
                        one = [cls new];
                        [one yy_modelSetWithDictionary:value];
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
                    }
                }
            } break;
                
            case YYEncodingTypeClass: {
                if (isNull) {
                    ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL);
                } else {
                    Class cls = nil;
                    if ([value isKindOfClass:[NSString class]]) {
                        cls = NSClassFromString(value);
                        if (cls) {
                            ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)cls);
                        }
                    } else {
                        cls = object_getClass(value);
                        if (cls) {
                            if (class_isMetaClass(cls)) {
                                ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)value);
                            }
                        }
                    }
                }
            } break;
                
            case  YYEncodingTypeSEL: {
                if (isNull) {
                    ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)NULL);
                } else if ([value isKindOfClass:[NSString class]]) {
                    SEL sel = NSSelectorFromString(value);
                    if (sel) ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)sel);
                }
            } break;
                
            case YYEncodingTypeBlock: {
                if (isNull) {
                    ((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())NULL);
                } else if ([value isKindOfClass:YYNSBlockClass()]) {
                    ((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())value);
                }
            } break;
                
            case YYEncodingTypeStruct:
            case YYEncodingTypeUnion:
            case YYEncodingTypeCArray: {
                if ([value isKindOfClass:[NSValue class]]) {
                    const char *valueType = ((NSValue *)value).objCType;
                    const char *metaType = meta->_info.typeEncoding.UTF8String;
                    if (valueType && metaType && strcmp(valueType, metaType) == 0) {
                        [model setValue:value forKey:meta->_name];
                    }
                }
            } break;
                
            case YYEncodingTypePointer:
            case YYEncodingTypeCString: {
                if (isNull) {
                    ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL);
                } else if ([value isKindOfClass:[NSValue class]]) {
                    NSValue *nsValue = value;
                    if (nsValue.objCType && strcmp(nsValue.objCType, "^v") == 0) {
                        ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, nsValue.pointerValue);
                    }
                }
            } // break; commented for code coverage in next line
                
            default: break;
        }
    }
}

最后的赋值都采用setter方法直接赋值

只有setter方法不存在时,降级处理,用KVC或者直接操作ivar内存偏移赋值。

采用setter方法而不是KVC的原因:

  • setter性能远高于KVC。
    KVC内部流程为先找setter,找不到再找ivar,中间层层拦截、检验等。而YYModel提前解析类信息时,已经把setter的SEL、IMP全部缓存进_YYModelPropertyMeta,直接objc_msgSend调用性能会更高。
  • 为了保留setter内部自定义逻辑。
    很多校验、格式处理、KVO触发、界面刷新等可能会写在setter里。如果直接操作实例变量,setter逻辑跳过,检验、KVO等全部失效。而KVC虽然也先执行setter,但中间多一层额外开销,稳定性、可控性不如直接调用setter。

Model->JSON

这里采用了NSJSONSerialization来将对象model转化为JSON数据。

NSJSONSerialization校验合法JSON对象的四大硬性规则:

  • JSON文件顶级根对象只能是NSDictionary或NSArray两种形态。
  • JSON容器内只允许5中类型:NSString、NSNumber、NSArray、NSDictionary、NSNull。
  • JSON中字典的key必须是NSString。
  • JSON中NSNumber类型的值不能是无穷大或非数值(NaN),否则序列化失败。

具体看一下ModelToJSONObjectRecursive这个方法实现:

objc 复制代码
// 递归转换模型到JSON,转换异常返回nil
static id ModelToJSONObjectRecursive(NSObject *model) {
    if (!model || model == (id)kCFNull) return model;
    if ([model isKindOfClass:[NSString class]]) return model;
    if ([model isKindOfClass:[NSNumber class]]) return model;
    // 处理NSDictionary
    if ([model isKindOfClass:[NSDictionary class]]) {
        // 如果可以直接转换为JSON数据,直接返回
        if ([NSJSONSerialization isValidJSONObject:model]) return model;
        NSMutableDictionary *newDic = [NSMutableDictionary new];
        // 遍历model的key和value
        [((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
            NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description;
            if (!stringKey) return;
            // 递归解析value
            id jsonObj = ModelToJSONObjectRecursive(obj);
            if (!jsonObj) jsonObj = (id)kCFNull;
            newDic[stringKey] = jsonObj;
        }];
        return newDic;
    }
    // 处理NSSet
    if ([model isKindOfClass:[NSSet class]]) {
        NSArray *array = ((NSSet *)model).allObjects;
        if ([NSJSONSerialization isValidJSONObject:array]) return array;
        NSMutableArray *newArray = [NSMutableArray new];
        for (id obj in array) {
            if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
                [newArray addObject:obj];
            } else {
                id jsonObj = ModelToJSONObjectRecursive(obj);
                if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
            }
        }
        return newArray;
    }
    // 处理NSArray
    if ([model isKindOfClass:[NSArray class]]) {
        if ([NSJSONSerialization isValidJSONObject:model]) return model;
        NSMutableArray *newArray = [NSMutableArray new];
        for (id obj in (NSArray *)model) {
            if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
                [newArray addObject:obj];
            } else {
                id jsonObj = ModelToJSONObjectRecursive(obj);
                if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
            }
        }
        return newArray;
    }
    // 处理NSURL、NSAttributedString、NSDate、NSData
    if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString;
    if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string;
    if ([model isKindOfClass:[NSDate class]]) return [YYISODateFormatter() stringFromDate:(id)model];
    if ([model isKindOfClass:[NSData class]]) return nil;
    
    // 初始化模型元
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:[model class]];
    // 映射表为空直接不做解析返回nil
    if (!modelMeta || modelMeta->_keyMappedCount == 0) return nil;
    NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:64];
    // 性能优化:使用__unsafe_unretained来避免在遍历block中直接使用result指针造成不必要的retain和release开销
    __unsafe_unretained NSMutableDictionary *dic = result; // avoid retain and release in block
    // 遍历模型元属性映射字典
    [modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
        // 如果没有getter方法就跳过
        if (!propertyMeta->_getter) return;
        
        id value = nil;
        // 属性元属于CNumber(int、float、double等),从属性中使用getter方法得到对应值
        if (propertyMeta->_isCNumber) {
            value = ModelCreateNumberFromProperty(model, propertyMeta);
            // 属性元属于NSType(NSString等),利用getter方法拿到value
        } else if (propertyMeta->_nsType) {
            id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
            // 对拿到的value递归解析
            value = ModelToJSONObjectRecursive(v);
        } else {
            // 根据属性元的type做相应处理
            switch (propertyMeta->_type & YYEncodingTypeMask) {
                    // id:需要递归解析,解析失败就返回nil
                case YYEncodingTypeObject: {
                    id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                    value = ModelToJSONObjectRecursive(v);
                    if (value == (id)kCFNull) value = nil;
                } break;
                    // class:转为NSString,返回class名称
                case YYEncodingTypeClass: {
                    Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                    value = v ? NSStringFromClass(v) : nil;
                } break;
                    // SEL:转为NSString,返回给定SEL的字符串表现形式
                case YYEncodingTypeSEL: {
                    SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                    value = v ? NSStringFromSelector(v) : nil;
                } break;
                default: break;
            }
        }
        // 如果value不能被解析就跳过
        if (!value) return;
        
        // 属性元是keyPath映射
        if (propertyMeta->_mappedToKeyPath) {
            NSMutableDictionary *superDic = dic;
            NSMutableDictionary *subDic = nil;
            for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) {
                NSString *key = propertyMeta->_mappedToKeyPath[i];
                if (i + 1 == max) { // end
                    if (!superDic[key]) superDic[key] = value;
                    break;
                }
                
                subDic = superDic[key];
                if (subDic) {
                    if ([subDic isKindOfClass:[NSDictionary class]]) {
                        subDic = subDic.mutableCopy;
                        superDic[key] = subDic;
                    } else {
                        break;
                    }
                } else {
                    subDic = [NSMutableDictionary new];
                    superDic[key] = subDic;
                }
                superDic = subDic;
                subDic = nil;
            }
        } else {
            if (!dic[propertyMeta->_mappedToKey]) {
                dic[propertyMeta->_mappedToKey] = value;
            }
        }
    }];
    
    if (modelMeta->_hasCustomTransformToDictionary) {
        BOOL suc = [((id<YYModel>)model) modelCustomTransformToDictionary:dic];
        if (!suc) return nil;
    }
    return result;
}

主要步骤为:

  • 判断入参,满足条件就直接返回。
  • 如果model从属于NSType,就根据不同的类型做逻辑处理。
  • 如果以上条件不被满足,就用model的Class初始化一个模型元_YYModelMeta。
  • 判断模型元的映射关系,遍历映射拿到对应的键值存入字典并返回。

总结

通过源码,我们更进一步认识了YYModel拥有更多性能优势的原因:

  • 提前解析了Runtime类结构并全局缓存,避免重复遍历解析耗时。
  • 大量使用CoreFoundation C接口、内联函数等,规避OC消息发送的开销。
  • 不使用KVC,而是提前缓存setter的SEL、IMP,直接函数调用赋值。
  • 采用分类,基于NSObject分类扩展,模型无需继承基类,实现无侵入性,不额外占用实例内存。
相关推荐
brave_zhao3 小时前
head方法可以用于http url嗅探吗
学习
伶俜663 小时前
# [特殊字符] 零基础学 ArkUI 手势(专题五):从点击到多指触控,一网打尽 6 种手势
学习·华为·harmonyos
辰海Coding3 小时前
MiniSpring框架学习笔记-JDBC 访问框架:如何抽取 JDBC 模板并隔离数据库?
java·数据库·笔记·学习·spring
十月的皮皮3 小时前
C语言学习笔记20260609-字符串反转两种实现方法
c语言·笔记·学习
风华圆舞3 小时前
一个 Flutter 项目同时保留 Android、iOS、HarmonyOS 支持的实践
android·flutter·ios
咸鱼翻身小阿橙3 小时前
C# WinForms 控件学习项目
开发语言·学习·c#
2501_915921433 小时前
uni-app 上架 iOS 的完整流程(无需依赖 Mac)
android·macos·ios·小程序·uni-app·iphone·webview
段一凡-华北理工大学3 小时前
工业领域的Hadoop架构学习~系列文章22:Hadoop生态展望 - 面向未来的技术演进
大数据·人工智能·hadoop·分布式·学习·架构·高炉炼铁
Fatbobman(东坡肘子)3 小时前
WWDC 2026 初印象:符合预期,但更务实 -- 肘子的 Swift 周报 #139
人工智能·macos·ios·swiftui·swift·wwdc