【iOS】源码学习-YYModel源码学习
前言
之前学习了YYModel的用法等,这里主要学习一下其源码。
YYModel源码分析
YYModel本质分为YYClassInfo和NSObject+YYModel两个模块。
- YYClassInfo:主要将Runtime层级的一些结构封装到NSObject层以便调用。
- NSObject+YYModel:主要负责提供方便调用的接口以及实现具体的模型转换逻辑。

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

YYClassInfo核心结构体
这里一一对比YYClassInfo和Runtime层级的一些结构体的关系:
- 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转模型、类型匹配时直接通过枚举判断,提升转换速度。
- 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,可跳过整套消息查找流程,性能得到巨大提升。
- 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、协议、完整类型编码等。
- 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;
}
整个初始化步骤就是:
- 创建单例缓存、类缓存和元类缓存
- 使用dispatch_semaphore作为锁保证缓存线程安全
- 先去缓存查找是否已经向缓存中注册了当前要初始化的YYClassInfo。如果找到,则判断缓存对象是否需要更新并执行相关操作;反之未找到就执行初始化。
- 初始化完成后向缓存中注册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分类扩展,模型无需继承基类,实现无侵入性,不额外占用实例内存。
