一、框架的核心思路
框架解决的问题,就是实现 json 和 OC对象 间的转换,这个过程的核心问题就是 json数据 和 OC对象的成员变量 之间的映射关系。
而这个映射关系,需要借助 runtime 来完成。只需要传入一个 Class 类变量,框架内部就能通过 runtime 将该类的属性以及方法查找出来,默认是将属性名作为映射的 key,然后 json 数据就能通过这个映射的 key 匹配赋值(通过 objc_msgSend而不是KVC)。
二、类型编码 Type-Encoding
可以通过 runtime 获取到某个类的所有属性名字,达成映射。但是考虑到我们的 模型类 往往会定义很多种类型,比如:double、char、NSString、NSDate、SEL 、NSSet 等,所以需要将源数据 json(或者字典数据)转换成我们实际需要的类型。
但是,计算机如何知道我们定义的 模型类 的属性是什么类型的呢?由此,引入类型编码的概念
Type-Encoding 是指定的一套类型编码,在使用 runtime 获取某个类的成员变量、属性、方法的时候,能同时获取到它们的类型编码,通过这个编码就能辨别这些成员变量、属性、方法的数据类型(也包括属性修饰符、方法修饰符等)。
三、将底层数据装进中间类
YYClassIvarInfo ==== Ivar
YYClassMethodInfo ==== Method
YYClassPropertyInfo ==== objc_property_t
YYClassInfo====Class
YYClassInfo 结构
@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
...
可以看到,Class 类的成员变量、属性、方法分别装入了三个 hash 容器(ivarInfos/methodInfos/propertyInfos)。
superClassInfo 指向父类,初始化时框架会循环向上查找,直至当前 Class 的父类不存在(NSObject 父类指针为 nil),这类似一个单向的链表,将有继承关系的类信息全部串联起来。这么做的目的,就是为了 json 转模型的时候,同样把父类的属性名作为映射的 key。初始化 YYClassInfo 的代码大致如下:
- (instancetype)initWithClass:(Class)cls {
if (!cls) return nil;
self = [super init];
...
//_update方法就是将当前类的成员变量列表、属性列表、方法列表转换放进对应的 hash
[self _update];
//获取父类信息。 classInfoWithClass: 是一个获取类的方法,里面有缓存机制,下一步会讲到
_superClassInfo = [self.class classInfoWithClass:_superCls];
return self;
}
YYClassInfo 缓存
作者做了一个类信息(YYClassInfo)缓存的机制:
+ (instancetype)classInfoWithClass:(Class)cls {
if (!cls) return nil;
//初始化几个容器和锁
static CFMutableDictionaryRef classCache;
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_wait(lock, DISPATCH_TIME_FOREVER);
YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
//更新成员变量列表、属性列表、方法列表
if (info && info->_needUpdate) [info _update];
dispatch_semaphore_signal(lock);
//若无缓存,将 Class 类信息转换为新的 YYClassInfo 实例,并且放入缓存
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;
}
由于同一个类的相关信息在程序运行期间通常是相同的,所以使用 classCache(类hash) 和 metaCache(元类hash) 缓存已经通过 runtime 转换为 YYClassInfo 的 Class,保证不会重复转换 Class 类信息做无用功;考虑到 runtime 带来的动态特性,作者使用了一个 bool 值判断是否需要更新成员变量列表、属性列表、方法列表,_update方法就是重新获取这些信息。
这个缓存机制能带来很高的效率提升,是 YYModel 一个比较核心的操作。
有几个值得注意和学习的地方:
1 使用 static 修饰局部变量提升其生命周期,而又不改变其作用域,保证在程序运行期间局部变量不会释放,又防止了其他代码对该局部变量的访问。
2 线程安全的考虑。在初始化 static 变量的时候,使用dispatch_once()保证线程安全;在读取和写入使用 dispatch_semaphore_t信号量保证线程安全。
四、一些工具方法
尽量用纯 C 函数、内联函数
使用纯 C 函数可以避免 ObjC 的消息发送带来的开销。如果 C 函数比较小,使用 inline 可以避免一部分压栈弹栈等函数调用的开销。
六、辅助类 _YYModelMeta
_YYModelMeta 是核心辅助类:
@interface _YYModelMeta : NSObject {
@package
YYClassInfo *_classInfo;
/// Key:mapped key and key path, Value:_YYModelPropertyMeta.
NSDictionary *_mapper;
/// Array<_YYModelPropertyMeta>, all property meta of this model.
NSArray *_allPropertyMetas;
/// Array<_YYModelPropertyMeta>, property meta which is mapped to a key path.
NSArray *_keyPathPropertyMetas;
/// Array<_YYModelPropertyMeta>, property meta which is mapped to multi keys.
NSArray *_multiKeysPropertyMetas;
/// The number of mapped key (and key path), same to _mapper.count.
NSUInteger _keyMappedCount;
/// Model class type.
YYEncodingNSType _nsType;
BOOL _hasCustomWillTransformFromDictionary;
BOOL _hasCustomTransformFromDictionary;
BOOL _hasCustomTransformToDictionary;
BOOL _hasCustomClassFromDictionary;
}
@end
自定义容器元素类型
同样是 YYModel 协议下的方法:modelContainerPropertyGenericClass,返回了一个自定义的容器与内部元素的 hash。比如模型中一个容器属性 @property NSArray *arr;,当你希望转换过后它内部装有CustomObject类型时,你需要实现该协议方法,返回 {@"arr":@"CustomObject"} 或者 @{@"arr": CustomObject.class}(看上面代码可知作者做了兼容)。
查找该类的所有属性
@implementation _YYModelMeta
- (instancetype)initWithClass:(Class)cls {
...
NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
YYClassInfo *curClassInfo = classInfo;
//循环查找父类属性,但是忽略跟类 (NSObject/NSProxy)
while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy)
for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) {
if (!propertyInfo.name) continue;
//兼容黑名单和白名单
if (blacklist && [blacklist containsObject:propertyInfo.name]) continue;
if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue;
//将属性转换为中间类
_YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
propertyInfo:propertyInfo
generic:genericMapper[propertyInfo.name]];
...
//记录
allPropertyMetas[meta->_name] = meta;
}
//指针向父类推进
curClassInfo = curClassInfo.superClassInfo;
}
...
自定义映射关系
modelCustomPropertyMapper 协议方法是用于自定义映射关系,比如需要将 json 中的 id 字段转换成属性:@property NSString *ID;,由于系统是默认将属性的名字作为映射的依据,所以这种业务场景需要使用者自行定义映射关系。
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"name" : @"n",
@"page" : @"p",
@"desc" : @"ext.desc",
@"bookID": @[@"id", @"ID", @"book_id"]};
}
七、给数据模型属性赋值 / 将数据模型解析成 json
NSObject+YYModel.m 中有个很长的方法:
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta) {...}
看该方法的名字应该很容易猜到,这就是将数据模型(model)中的某个属性(meta)赋值为目标值(value)。具体代码不贴了,主要是根据之前的一些辅助的类,利用 objc_msgSend 给目标数据 model 发送属性的 setter 方法。代码看起来复杂,实际上很简单。
性能的优化
直接使用 objc_msgSend给对象发送消息的效率要高于使用 KVC,可以在源码中看到作者但凡可以使用发送消息赋值处理的,都不会使用 KVC。










网友评论