简介

YYModel本质上分为YYClassInfo和NSObject+YYModel两个模块。
- YYClassInfo:主要讲Runtime层级的一些结构封装到NSOebjct层以便调用
- NSObject+YYModel:主要负责提供方便调用的接口以及实现具体的模型转换逻辑
YYClassInfo解析

我们逐个看一下他们之间的对应关系:
YYClassInfo && objc_ivar

我们在看一下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
}
可以发现所有的NSString类型都使用的是strong修饰符,这是因为所有的字符串都是通过Runtime方法拿到的const char* 之后通过stringWithUTF8String转换为NSString类型的,这里使用strong做一个次强引用的效率比copy高。原因如下:
- 数据天然不可变:ivar_getName返回的const char*指向的是runtime内核静态常量,永远不会修改、释放。通过stringWithUTF8String:生成的也是不可变的NSString
- strong比起copy而言,做类型判断的开销损耗更小
使用枚举法判断类型,转换速度较高
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 // 参数类型编码
//源码
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE; // 方法名称
char * _Nullable method_types OBJC2_UNAVAILABLE; // 方法类型
IMP _Nonnull method_imp OBJC2_UNAVAILABLE; // 方法实现(函数指针)
}
YYClassMethodInfo在初始化时调用Runtime API将原始方法信息拆开存储。提升转换速度
同时YYModel提前缓存IMP有效避免了常规发送流程(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
struct property_t {
const char *name; // 名称
const char *attributes;
};
可以发现原生的property_t结构中,getter、setter、ivar、class、协议等信息都需要手动去解析attributes字符串得到,但是YYClassPropertyInfo实现了完整的解析封装,初始化时拆解attributes字符串,优先读取自定义getter、setter方法,没有的话就按照规则拼接,同时解析出其他的数据。
YYClassInfo和objc_class
objc
/**
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
/**
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.
*/
+ (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.
*/
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;
@end
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中都是单向链表,查找某个属性或者变量啥的都需要顺序遍历,速度慢耗时。而YYClassInfo初始化的时候一次性遍历全部并存储到字典中去,大幅度提升性能。
YYClassInfo出初始化
objc
+ (instancetype)classInfoWithClass:(Class)cls {
if (!cls) return nil;
static CFMutableDictionaryRef classCache;//类缓存
static CFMutableDictionaryRef metaCache;//元类缓存
/*
classCache:缓存实例方法、属性、成员变量等普通类信息
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_wait(lock, DISPATCH_TIME_FOREVER);
YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));//判断当前cls是普通类还是元类,从对应缓存中查找YYClassInfo
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);
CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));//写缓存
dispatch_semaphore_signal(lock);
}
}
return info;
}
我们使用自然语言描述一下上面的过程:
- 创建单例缓存(类缓存和元类缓存)
- 使用信号量作为锁保证缓存线程的安全性
- 初始化之前先去缓存中查找是否已经向缓存中注册了当前要初始化的YYClassInfo,如果找到就判断需不需要更新与相关操作。反之初始化
- 初始化完成之后向缓存中写入YYClassInfo实例
NSObject+ YYModel
这是利用YYClassInfo层级封装好的类执行JSON、模型之间相互转化的一个逻辑,且提供给类一个无侵入性入口:'
主要有以下四个模块:
类型编码解析
主要通过YYEncodingNSType枚举来细分Foundation系统对象、YYClassGetNSType函数来将NS类型转换为YYEncodingType、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;
}
这部分将cls快速转换为枚举值,方便后面对于不同类型进行一个处理。
下面我们看一下解析类型编码时如果是block类型,需要做特殊处理:()
objc
static force_inline Class YYNSBlockClass() {
static Class cls;//保存最终查找的block公共基类
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
void (^block)(void) = ^{};//创建一个空Block
cls = ((NSObject *)block).class;
//向上查找,直至找到BSObject的直接子类
while (class_getSuperclass(cls) != [NSObject class]) {
cls = class_getSuperclass(cls);
}
});
return cls; // current is "NSBlock"
}
栈、堆、全局block运行时类名不一样,直接对比会判断失败。而它们拥有一个公共父类__NSBlock,因此拿这个基类统一判断是否为block类型。
数据结构的定义
NSObject+YYModel中重新定义了两个类,通过它们使用YYClassInfo中的封装:
_YYModelPropertyMeta
这是一个模型对象中的属性元数据类,包含YYClassPropertyInfo。
用来描述类中的某一个属性,记录属性叫啥、啥类型、对应JSON哪个key、getter/setter是啥,以及如何完成赋值。
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//是不是C语言数字
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//是否可以使用KVC
BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver//是否可以被NSKeyedArchiver解档归档
BOOL _hasCustomClass//FromDictionary; ///< 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
假设有一个模型:
objc@interface User : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSInteger age; @property (nonatomic, strong) NSArray<Book *> *books; @endYYModel会为每一个属性创建一个_YYModelPropertyMeta
name → 一个 _YYModelPropertyMeta
age → 一个 _YYModelPropertyMeta
books → 一个 _YYModelPropertyMeta
_YYModelMeta
整个模型类的元数据集合,是一个模型类全局转换配置,持有全部属性meta数组、全局映射规则、黑白名单等
Objective-C Runtime 原始信息
↓ 封装
YYClassInfo
↓ 再加工,加入 JSON 映射及转换规则
_YYModelMeta
↓
包含多个 _YYModelPropertyMeta
objc
@interface _YYModelMeta : NSObject {
@package
//保存当前模型类的runtime信息,即对runtime原始数据的面向对象封装
YYClassInfo *_classInfo;
//用于根据JSON字段快速找到对应的_YYModelPropertyMeta,利用哈希表快速找到属性
NSDictionary *_mapper;
//保存当前模型中所有可能参与转换的属性,每个元素都是_YYModelPropertyMeta
NSArray *_allPropertyMetas;
//保存映射到多级keyPath的属性
NSArray *_keyPathPropertyMetas;
//保存一个模型属性可以对应多个候选 JSON key 的属性元数据,第一个有效key会作为主要映射key
NSArray *_multiKeysPropertyMetas;
//一个模型属性可以对应多个候选 JSON key 的属性元数据
NSUInteger _keyMappedCount;
//记录当前整个模型类属于哪一种 Foundation 类型。自定义模型对应YYEncodingTypeNSUnknown
YYEncodingNSType _nsType;
BOOL _hasCustomWillTransformFromDictionary;
BOOL _hasCustomTransformFromDictionary;
BOOL _hasCustomTransformToDictionary;
BOOL _hasCustomClassFromDictionary;
}
@end
//mapper查找逻辑:
_YYModelPropertyMeta *propertyMeta =
[meta->_mapper objectForKey:key];
while (propertyMeta) {
ModelSetValueForProperty(model, value, propertyMeta);
propertyMeta = propertyMeta->_next;
}
同一个JSON字段可以同时给多个模型属性赋值。
字典模型转换底层实现
下面我们学习一下YYModel中字典与模型双向转换的底层实现:
JSON -> Model
objc
+ (instancetype)yy_modelWithJSON:(id)json {
NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
return [self yy_modelWithDictionary:dic];
}
可以看到,JSON转换为模型主要分为两步:
JSON -> NSDictionary
objc
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
if (!json || json == (id)kCFNull) return nil;//kCFNull 是 Core Foundation 中表示空值的单例对象
NSDictionary *dic = nil;
NSData *jsonData = nil;
if ([json isKindOfClass:[NSDictionary class]]) {
dic = json;
} else if ([json isKindOfClass:[NSString class]]) {
//如果传入的是字符串就用UTF-8编码转换为二进制数据
jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
} else if ([json isKindOfClass:[NSData class]]) {
jsonData = json;
}
if (jsonData) {
dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
}
return dic;
}
其中的KCFNull是CF框架中CFNull的单例对象,CFNull是用来表示集合对象中的空值(不允许为NULL)。CFNull对象既不允许被创建也不允许被销毁,而是通过定义一个CFNull常量,即kCFNull,在需要空值的时候使用。
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;
}
这一部分负责分析模型属性,从字典中取出值后做类型转换,然后调用方法将字典中的值赋到模型属性中去。
接下来就是转换核心部分:
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)];//获取当前对象对应的模型元数据
if (modelMeta->_keyMappedCount == 0) return NO;//检查是否有可映射属性(属性只读或者modelPropertyBlacklist方法中忽略了就不能算有效映射)
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,将字典中的数据填充到模型层中
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 {
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
//赋值完成之后执行自定义转换(可以生成派生属性或者判断模型是否有效)
if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
yy_modelSetWithDictionary:是 YYModel 真正执行字典赋值的入口。它先检查参数是否为有效字典,然后通过object_getClass(self)获取当前对象的真实运行时类型,并取得缓存的_YYModelMeta。元数据里保存了模型属性、setter、类型、JSON 映射、keyPath 和多 key 等信息。如果模型实现了
modelCustomWillTransformFromDictionary:,会先对原始字典进行预处理。接着 YYModel 把模型、字典和元数据放进ModelSetContext,供 Core Foundation 的 C 回调函数使用。为了提高性能,它会比较模型映射属性数量和字典字段数量。如果字典字段少,就遍历字典,通过
_mapper查找对应属性;如果模型属性少,就遍历属性元数据,再到字典中取值。字典遍历无法直接完整处理 keyPath 和多 key,所以还要额外遍历对应的属性数组。属性值最终会进入
ModelSetValueForProperty,在这里完成数字、字符串、嵌套模型、容器泛型等类型转换,并调用 setter 赋值。所有属性处理完后,如果模型实现了modelCustomTransformFromDictionary:,还会执行最终的数据加工或合法性校验,并将其返回值作为整个转换结果
通过上面的代码,我们可以知道核心调用方法是一个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)];//根据JSON key查找属性元数据
__unsafe_unretained id model = (__bridge id)(context->model);
while (propertyMeta) {
if (propertyMeta->_setter) {
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
}
propertyMeta = propertyMeta->_next;
};
}
ModelSetWithDictionaryFunction是 YYModel 遍历 JSON 字典时使用的 C 回调函数。每遍历到一个 key-value,先从 context 中取出模型元数据和目标模型,再通过_mapper按 JSON key 查找对应的_YYModelPropertyMeta。如果属性存在 setter,就调用ModelSetValueForProperty,根据属性的类型信息完成类型转换并调用 setter 赋值。由于多个属性可能映射到同一个 JSON key,所以_YYModelPropertyMeta通过_next组成链表,函数会通过 while 循环把同一个 value 依次赋给所有相关属性。这里使用 Core Foundation 遍历、哈希映射、缓存 setter 和__unsafe_unretained,主要目的是降低高频字典转模型时的性能开销
接下来就是真正实现赋值的方法:modelSetValueForProperty方法:
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;
}
}
}
通过判断属性类型对model层的数据进行赋值:
如果属性元是CNumber类型:使用ModelSetNumberToProperty赋值
如果是NSType类型的(如NSString、NSNumber):根据类型转换中可能涉及的对应类型做逻辑判断并赋值
如果属性元既不是CNumber与NSType(可猜测id、Class、SEL、Block、struct、union、charn、void或char类型):猜测类型并做出相应转换和赋值
Model->JSON
实现从Model转换为JSON,如下函数:
objc
- (id)yy_modelToJSONObject {
/*
Apple said:
The top level object is an NSArray or NSDictionary.
All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
All dictionary keys are instances of NSString.
Numbers are not NaN or infinity.
*/
id jsonObject = ModelToJSONObjectRecursive(self);//将模型对象递归转换为一个符合JSON规范的NSArray或者NSDictionary,这一步还没有生成真正的JSON字符串,只是生成了一个被NSJSONSerialization序列化的Foundation对象
if ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject;
if ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject;
return nil;
}
- (NSData *)yy_modelToJSONData {
id jsonObject = [self yy_modelToJSONObject];
if (!jsonObject) return nil;
return [NSJSONSerialization dataWithJSONObject:jsonObject options:0 error:NULL];
}
- (NSString *)yy_modelToJSONString {
NSData *jsonData = [self yy_modelToJSONData];
if (jsonData.length == 0) return nil;
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
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;
if ([model isKindOfClass:[NSDictionary class]]) {
if ([NSJSONSerialization isValidJSONObject:model]) return model;
NSMutableDictionary *newDic = [NSMutableDictionary new];
[((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description;
if (!stringKey) return;
id jsonObj = ModelToJSONObjectRecursive(obj);
if (!jsonObj) jsonObj = (id)kCFNull;
newDic[stringKey] = jsonObj;
}];
return newDic;
}
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;
}
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;
}
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]];
if (!modelMeta || modelMeta->_keyMappedCount == 0) return nil;
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:64];
__unsafe_unretained NSMutableDictionary *dic = result; // avoid retain and release in block
[modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
if (!propertyMeta->_getter) return;
id value = nil;
if (propertyMeta->_isCNumber) {
value = ModelCreateNumberFromProperty(model, propertyMeta);
} else if (propertyMeta->_nsType) {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = ModelToJSONObjectRecursive(v);
} else {
switch (propertyMeta->_type & YYEncodingTypeMask) {
case YYEncodingTypeObject: {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = ModelToJSONObjectRecursive(v);
if (value == (id)kCFNull) value = nil;
} break;
case YYEncodingTypeClass: {
Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromClass(v) : nil;
} break;
case YYEncodingTypeSEL: {
SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromSelector(v) : nil;
} break;
default: break;
}
}
if (!value) return;
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性能优化
-
在Model和JSON转换过程中需要很多类的元数据,YYModel通过CFDictionaryCreateMutable方法将数据存入缓存中,需要的时候直接拿出来使用,避免了重复解析类的操作。
-
JSONModel中使用KVC将相应的JSON数据赋值给属性,但如果考虑性能的话,Getter和Setter方法的性能要优于KVC,YYModel就是使用getter和setter方法进行赋值,性能优越一点
-
尽量使用纯C函数、内联函数:YYModel的实现中包含大量纯C函数、内联函数,有效的避免了objc的消息转发机制带来的开销。
-
在Model的属性个数和JSON的键值对个数都是已知的时,选择数量较少的一方进行遍历,减少循环次数、节省时间
-
采用分类,基于NSObject分类拓展,模型无需继承基类,实现无侵入性,不额外占用实例内存
架构分析
YYModel将整个模型转换分为了三层:
第一层:Runtime 原始信息层
YYClassInfo / YYClassPropertyInfo
第二层:模型转换元数据层
_YYModelMeta / _YYModelPropertyMeta
第三层:转换执行层
ModelSetValueForProperty
ModelToJSONObjectRecursive
第一次遇到某个Model类时,通过Runtime分析属性,将属性类型、setter、getter、JSON映射关系、容器泛型等信息编译为元数据并缓存,后续再转换这个类时直接使用缓存信息完成赋值。
runtime解析只做一次,后续转换主要是哈希查询、类型判断和直接调用setter,是高性能、自动类性转换、类型安全且非侵入式的模型框架/