美文网首页
YYModel分析

YYModel分析

作者: Crazy2015 | 来源:发表于2019-04-21 15:24 被阅读0次
一、框架的核心思路

框架解决的问题,就是实现 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。

参考:https://cloud.tencent.com/developer/article/1148492

相关文章

  • YYImage/YYModel/YYCache

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

  • YYModel分析

    一、框架的核心思路 框架解决的问题,就是实现 json 和 OC对象 间的转换,这个过程的核心问题就是 json数...

  • YYModel源码分析

    前言 YYModel 是一个iOS JSON模型转化库,和其他一些同类型库相比,具有比较好的性能优势。本文会对YY...

  • YYModel源码分析(一)

    本文章所使用的YYModel源码基于0.9.8版本。从截图来看,YYModel是由两个类构成,本章先着手分析YYC...

  • YYModel原理

    YYModel原理分析: http://blog.csdn.net/u011619283/article/deta...

  • iOS源码解析—YYModel(NSObject+YYModel

    概述 ​ iOS源码解析—YYModel(YYClassInfo)分析了如何根据OC的Class对象构建...

  • 《YYModel源码分析(二)NSObject+YYModel》

    承接上文《YYModel源码分析(一)YYClassInfo》之前文章讲述了YYClassInfo如何将runti...

  • YYModel源码分析(一)

    基础知识记录 oc运行时定义的几种类型介绍:Class:objective-c中类的定义Ivar:对象的实例变量,...

  • YYModel内部分析

    首先介绍YYClassInfo,这个类主要保存的是类似于objc_class结构体 YYClassIvarInfo...

  • YYModel源码分析(二)

    下面分析NSObject+YYModel的实现,从头文件可以看该类由三个Category和一个Protocol所组...

网友评论

      本文标题:YYModel分析

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