前言:
阅读YYModel之前建议先阅读Runtime基础篇,YYModel采用Runtime直接调用 Getter/Setter,是一款高性能 iOS/OSX 模型转换框架,支持定义映射过程。正好最近想深入Runtime一番,于是就读了几遍YYModel的源码,本文将从作者提供的性能优化的几个 Tip,还有结合Github 上的 Issues,以及相关项目线索为起点对YYModel源码进行分析,并且绘制一张项目的整体架构图,设计的思想。
源码来源:
资料参考文献
Type Encodings
Declared Properties
Runtime参考文档
项目整体架构:
项目图
Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png![Uploading Paste_Image_669357.png . . .]
Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png类与对象的继承层次关系如图1-1
图1-1 Google sourceYYModel层次关系
YYClassInfo层次关系
YYClassInfo设计图注意:所指定的方向是包含关系,例如YYClassInfo包含class,同时YYClassInfo又包含YYClassMethodInfo、YYClassPropertyInfo、YYClassIvarInfo,其实用继承关系理解是最好,这样可以和类与关系图思想来理解更好(所以下面的图,就画成继承关系,便于结合类与关系继承理解),但是准确来说是包含关系。
- YYClassIvarInfo 对Runtime Ivar进行封装
- YYClassMethodInfo 对Runtime Method进行封装
- YYClassPropertyInfo 对Runtime Property进行封装
- YYClassInfo 对YYClassIvarInfo、YYClassMethodInfo、YYClassPropertyInfo、以及Runtime Class进行封装
_YYModelPropertyMeta层次关系
_YYModelPropertyMeta层次关系可以分为两种,一种是_YYModelPropertyMeta对YYClassInfo的封装,另一种是_YYModelPropertyMeta对YYClassPropertyInfo的封装。
** _YYModelPropertyMeta对YYClassInfo封装**
Class包含关系图 Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png- 每一份Class都有可能嵌套Class,那么Class就有可能要实现多级类映射Class关系,而且_YYModelPropertyMeta提供了_next指针便于指向上一级属性映射关系
- YYClassInfo类提供_superCls遍历父类class信息,后面会详细说明
- _YYModelPropertyMeta包含YYClassInfo信息
** _YYModelPropertyMeta对YYClassPropertyInfo封装**
_YYModelPropertyMeta设计图1- 每一份Class都有可能嵌套Class,,那么Class就有可能要实现多级类映射Property关系,而且YYModelProperyMeta提供_mappedToKeyArray、_mappedToKeyPath属性来判断多级属性映射关系
YYClassInfo.h
类型编码
YYClassInfo提供了三种枚举类型分别是Foundation Framework 编码、 Qualifier限定符编码、 Property属性编码其对应类型编码文档Table 6-1、Table 6-2、Table 7-1.
typedef NS_OPTIONS(NSUInteger, YYEncodingType) {
typedef NS_OPTIONS(NSUInteger, YYEncodingType) {
// Table 6-1 Foundation Framework 编码
YYEncodingTypeMask = 0xFF, ///< mask of type value 表示低8位的十六进制Mask掩码,用于得到枚举值的低8位值(0 --> 8位),对0xFF按位与运算,可以避免误差
YYEncodingTypeUnknown = 0, ///< unknown 类型编码未知
YYEncodingTypeVoid = 1, ///< void
YYEncodingTypeBool = 2, ///< bool
YYEncodingTypeInt8 = 3, ///< char / BOOL
YYEncodingTypeUInt8 = 4, ///< unsigned char
YYEncodingTypeInt16 = 5, ///< short
YYEncodingTypeUInt16 = 6, ///< unsigned short
YYEncodingTypeInt32 = 7, ///< int
YYEncodingTypeUInt32 = 8, ///< unsigned int
YYEncodingTypeInt64 = 9, ///< long long
YYEncodingTypeUInt64 = 10, ///< unsigned long long
YYEncodingTypeFloat = 11, ///< float
YYEncodingTypeDouble = 12, ///< double
YYEncodingTypeLongDouble = 13, ///< long double
YYEncodingTypeObject = 14, ///< id
YYEncodingTypeClass = 15, ///< Class
YYEncodingTypeSEL = 16, ///< SEL
YYEncodingTypeBlock = 17, ///< block
YYEncodingTypePointer = 18, ///< void*
YYEncodingTypeStruct = 19, ///< struct
YYEncodingTypeUnion = 20, ///< union
YYEncodingTypeCString = 21, ///< char*
YYEncodingTypeCArray = 22, ///< char[10] (for example)
// Table 6-2 Qualifier限定符编码
YYEncodingTypeQualifierMask = 0xFF00, ///< mask of qualifier 表示8 ~ 15位 的十六进制Mask掩码,用于得到枚举值的低15位值
YYEncodingTypeQualifierConst = 1 << 8, ///< const
YYEncodingTypeQualifierIn = 1 << 9, ///< in
YYEncodingTypeQualifierInout = 1 << 10, ///< inout
YYEncodingTypeQualifierOut = 1 << 11, ///< out
YYEncodingTypeQualifierBycopy = 1 << 12, ///< bycopy
YYEncodingTypeQualifierByref = 1 << 13, ///< byref
YYEncodingTypeQualifierOneway = 1 << 14, ///< oneway
// Table 7-1 Property属性编码
YYEncodingTypePropertyMask = 0xFF0000, ///< mask of property 表示16 ~ 24位 的十六进制Mask掩码,用于得到枚举值的低24位值
YYEncodingTypePropertyReadonly = 1 << 16, ///< readonly
YYEncodingTypePropertyCopy = 1 << 17, ///< copy
YYEncodingTypePropertyRetain = 1 << 18, ///< retain
YYEncodingTypePropertyNonatomic = 1 << 19, ///< nonatomic
YYEncodingTypePropertyWeak = 1 << 20, ///< weak
YYEncodingTypePropertyCustomGetter = 1 << 21, ///< getter=
YYEncodingTypePropertyCustomSetter = 1 << 22, ///< setter=
YYEncodingTypePropertyDynamic = 1 << 23, ///< @dynamic
};
- 枚举定义中,存在不同类型的定义(可以用0xFF、0xFF00、0xFF0000来区别),而不同的类型又需要组合。
- Mask枚举值,没有实际作用,只是用来获取低n位的枚举数值,让枚举值之间可以使用运算符|、&
- 使用NS_OPTIONS来定义具有位移的枚举类型,NS_ENUM是通用情况
YYClassMethodInfo.h
YYModel采用Runtime直接调用 Getter/Setter,所以要将Runtime Method属性暴露出来提供MethodInfo类获取相应的属性
Runtime:Method information.
Method information.
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}
YYClassMethodInfo
@interface YYClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method; ///< method
@property (nonatomic, strong, readonly) NSString *name; ///< method name
@property (nonatomic, assign, readonly) SEL sel; ///< method's selector
@property (nonatomic, assign, readonly) IMP imp; ///< method's implementation
@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
- (instancetype)initWithMethod:(Method)method;
@end
Runtime:Ivar information.
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE; // 变量名
char *ivar_type OBJC2_UNAVAILABLE; // 变量类型
int ivar_offset OBJC2_UNAVAILABLE; // 基地址偏移字节
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
** YYClassIvarInfo**
@interface YYClassIvarInfo : NSObject
@property (nonatomic, assign, readonly) Ivar ivar; ///< ivar
@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
- (instancetype)initWithIvar:(Ivar)ivar;
@end
Runtime:Class information.
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
YYClassInfo.m
@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
....
YYEncodingGetType方法
通过指定类型编码字符串,返回类型编码字符串中Foundation Framework 编码字符和method encodings编码字符
// 通过指定类型编码字符串,返回类型编码字符串中Foundation Framework 编码字符和method encodings编码字符
YYEncodingType YYEncodingGetType(const char *typeEncoding) {
// 转换const限定符
char *type = (char *)typeEncoding;
// 传入字符串为NULL,返回未知类型
if (!type) return YYEncodingTypeUnknown;
size_t len = strlen(type);
// 类型编码字符串的长度为空,返回未知类型编码
if (len == 0) return YYEncodingTypeUnknown;
// @property (nonatomic(3:property), copy(2:property)) const(0:Qualifier) NSString(1:Foundation Framework ) *title(4); rT@"NSString",C,N,V_title
YYEncodingType qualifier = 0;
bool prefix = true;
// 可能多个编码字符(多种方法修饰)
while (prefix) {
/** for type qualifiers(方法编码限定符,其中switch对应类型编码文档:)
Code Meaning
r const
n in
N inout
o out
O bycopy
R byref
V oneway
*/
switch (*type) {
case 'r': {
qualifier |= YYEncodingTypeQualifierConst;
type++;
} break;
case 'n': {
qualifier |= YYEncodingTypeQualifierIn;
type++;
} break;
case 'N': {
qualifier |= YYEncodingTypeQualifierInout;
type++;
} break;
case 'o': {
qualifier |= YYEncodingTypeQualifierOut;
type++;
} break;
case 'O': {
qualifier |= YYEncodingTypeQualifierBycopy;
type++;
} break;
// bycopy 修饰的方法
case 'R': {
qualifier |= YYEncodingTypeQualifierByref;
type++;
} break;
// oneway 修饰的方法
case 'V': {
qualifier |= YYEncodingTypeQualifierOneway;
type++;
} break;
// 当前字符不再匹配 method encodings编码字符
default: { prefix = false; } break;
}
}
// 判断类型编码后续字符
len = strlen(type);
// 类型编码字符串的长度为空,返回字符串未知类型编码和method encodings限定符编码
if (len == 0) return YYEncodingTypeUnknown | qualifier;
switch (*type) {
/** For Foundation Framework
Code Meaning
c A char
i An int
s A short
l
A long l is treated as a 32-bit quantity on 64-bit programs.
q A long long
C An unsigned char
I An unsigned int
S An unsigned short
L An unsigned long
Q An unsigned long long
f A float
d A double
B A C++ bool or a C99 _Bool
v A void
* A character string (char *)
@ An object (whether statically typed or typed id)
# A class object (Class)
: A method selector (SEL)
[array type] An array
{name=type...} A structure
(name=type...) A union
bnum A bit field of num bits
^type A pointer to type
? An unknown type (among other things, this code is used for function pointers)
*/
case 'v': return YYEncodingTypeVoid | qualifier;
case 'B': return YYEncodingTypeBool | qualifier;
case 'c': return YYEncodingTypeInt8 | qualifier;
case 'C': return YYEncodingTypeUInt8 | qualifier;
case 's': return YYEncodingTypeInt16 | qualifier;
case 'S': return YYEncodingTypeUInt16 | qualifier;
case 'i': return YYEncodingTypeInt32 | qualifier;
case 'I': return YYEncodingTypeUInt32 | qualifier;
case 'l': return YYEncodingTypeInt32 | qualifier;
case 'L': return YYEncodingTypeUInt32 | qualifier;
case 'q': return YYEncodingTypeInt64 | qualifier;
case 'Q': return YYEncodingTypeUInt64 | qualifier;
case 'f': return YYEncodingTypeFloat | qualifier;
case 'd': return YYEncodingTypeDouble | qualifier;
case 'D': return YYEncodingTypeLongDouble | qualifier;
case '#': return YYEncodingTypeClass | qualifier;
case ':': return YYEncodingTypeSEL | qualifier;
case '*': return YYEncodingTypeCString | qualifier;
case '^': return YYEncodingTypePointer | qualifier;
case '[': return YYEncodingTypeCArray | qualifier;
case '(': return YYEncodingTypeUnion | qualifier;
case '{': return YYEncodingTypeStruct | qualifier;
case '@': {
if (len == 2 && *(type + 1) == '?')
return YYEncodingTypeBlock | qualifier;
else
return YYEncodingTypeObject | qualifier;
}
// 当前字符不再匹配 Foundation Framework 编码字符
default: return YYEncodingTypeUnknown | qualifier;
}
}
-
1.T@"NSDate",&,N,V_publishDate
-
2.T@?,C,N,V_ltBlock
-
3.T@,R,C,N,V_idReadonlyCopyNonatomic
-
'@' Meaning: An object (whether statically typed or typed id)如果是@那么,类型可能是对象或者是一个指向id,还有一种特殊情况,如果*(type + 1) (编码字符串下一位)是‘?’,或者当前所指向的编码字符长度为2,可能是类型可能是block(见2类型编码例子),
类型编码
实践一番类型编码看看编码具体格式,其中取出几个实例变量的类型编码讲解
LTBook.h
@interface LTBook : NSObject
@property (copy, nonatomic) NSString *name;
@property (nonatomic, assign) CGSize size;
@property NSString *desc;
@property (nonatomic, strong) NSDictionary *likedUsers; //
@property (nonatomic, strong) UIColor *color;
@property (assign, nonatomic) uint64_t pages;
@property (strong, nonatomic) NSDate *publishDate;
@property (readonly, copy, nonatomic) id idReadonlyCopyNonatomic;
@property (readonly, copy, nonatomic) NSString const *strName;
@property (readonly, copy, atomic) NSString *strName2;
@end
-
成员变量:pages 对应类型编码:propertiesTypeEncoding: TQ,N,V_pages
-
成员变量:publishDate 对应类型编码:propertiesTypeEncoding:T@"NSDate",&,N,V_publishDate
-
成员变量:publishDate 对应类型编码:propertiesTypeEncoding: T@"NSString",R,C,N,V_strName
-
第一个T是固定的
-
第一个属性T@“NSDate”,(指属性名所指向的类型为NSDate) 、T@(指属性名所指向的类型为id)、TQ(指属性名所指向的类型为uint64_t),更详细的可以查看 声明属性文档
Property Attribute Description Examples章节, -
第三个属性表示声明的Property attribute,详细可以查看编码类型枚举(或者查看官方文档Table 7-1 Declared property type encodings)
-
如果有第四第五个属性,表示也是表示属性类型编码
-
最后一个属性V_是固定的紧接着pages、strName指的是属性名
-
从打印的结果或官网提供的属性文档,还可以获取到一些信息,1.assign、readwrite、atomic没有对应的属性类型编码,2.类型编码字符串的顺序,strong, readWrite, nonatomic等属性顺序是有一定规律的,在编写property的属性时应该注意编写的顺序,还可以时刻提醒自己类型编码字符串的顺序。
initWithIvar方法
该方法获得相应的Ivar属性,存入YYClassiVarInfo对应的属性,这里就不多说了
initWithMethod方法
该方法获得相应的Method属性,存入YYClassiVarInfo对应的属性
- (instancetype)initWithMethod:(Method)method {
if (!method) return nil;
self = [super init];
_method = method;
_sel = method_getName(method);
_imp = method_getImplementation(method);
const char *name = sel_getName(_sel);
if (name) {
_name = [NSString stringWithUTF8String:name]; // 在所有Runtime 以char *定义的API都被视为UTF-8编码,所以这里用stringWithUTF8String
}
const char *typeEncoding = method_getTypeEncoding(method);
if (typeEncoding) {
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
}
char *returnType = method_copyReturnType(method);
if (returnType) {
_returnTypeEncoding = [NSString stringWithUTF8String:returnType]; // 该参数,暂无作用
free(returnType);
}
unsigned int argumentCount = method_getNumberOfArguments(method); // 该参数,暂无作用
if (argumentCount > 0) { // 这一块暂时无意义
NSMutableArray *argumentTypes = [NSMutableArray new];
for (unsigned int i = 0; i < argumentCount; i++) {
char *argumentType = method_copyArgumentType(method, i);
NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil;
[argumentTypes addObject:type ? type : @""];
if (argumentType) free(argumentType);
}
_argumentTypeEncodings = argumentTypes;
}
return self;
}
initWithProperty方法
- (instancetype)initWithProperty:(objc_property_t)property {
if (!property) {
return nil;
}
self = [self init];
_property = property;
const char *name = property_getName(property);
if (name) {
_name = [NSString stringWithUTF8String:name];
}
LTEncodingType type = 0;
unsigned int attrCount;
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
for (unsigned int i = 0; i < attrCount; i++) {
NSLog(@"attrs[i].name1[0]---%c",attrs[i].name[0]);
switch (attrs[i].name[0]) { // 或者属性每一个类型编码,见声明属性文档:Declared property type encodings章节
case 'T': { // Type encoding T@"NSString",C,N,V_name
if (attrs[i].value) { // attrs[i].value : Foundation Framework Code; Example: attrs[i].name[0] = T,attrs->value = @? / @"NSString"/ @ / @"NSDate" / Q / @"UIColor"
_typeEncoding = [NSString stringWithUTF8String:attrs[i].value]; // NSString
type = LTEncodingGetType(attrs[i].value); // attrs[i].value = @"NSString" 此时传进去的@"NSString" 通过LTEncodingGetType方法获得对应 对应限定符 LTEncodingTypeObject
if ((type & LTEncodingTypeMask) == LTEncodingTypeObject) {
size_t len = strlen(attrs[i].value); // @"NSString" 长度则为11
if (len > 3) { // 一般属性的类型长度大于3
char name[len - 2]; //原来是name[11] ,现在 11 - 2 name[9]
name[len - 3] = '\0'; // name[9] = '\0' 设置最后索引为'\0' ,目的是去掉最有一个“
memcpy(name, attrs[i].value + 2, len - 3); // 这行代码目的是获得属性名, 猜想:attrs[i].value + 2 = NSString",属性类型和属性名是共享一份内存的,这个方法copy一份内存到name,通过name就可以获得对应的属性定义的类型。
_cls = objc_getClass(name); // name所指向的具体来说内存是属性定义的类型 这里通过name 获得int 、NSString
}
}
}
} break;
// @property (nonatomic(3:property), copy(2:property)) const(0:Qualifier) NSString(1:Foundation Framework ) *title(4); rT@"NSString",C,N,V_title 获得 2 3 4 位置的编码类型
case 'V': { // Instance variable
if (attrs[i].value) {
_ivarName = [NSString stringWithUTF8String:attrs[i].value];
}
} break;
case 'R': {
type |= LTEncodingTypePropertyReadonly;
} break;
case 'C': {
type |= LTEncodingTypePropertyCopy;
} break;
case '&': {
type |= LTEncodingTypePropertyRetain;
} break;
case 'N': {
type |= LTEncodingTypePropertyNonatomic;
} break;
case 'D': {
type |= LTEncodingTypePropertyDynamic;
} break;
case 'W': {
type |= LTEncodingTypePropertyWeak;
} break;
case 'G': { // For Example:@property(getter=intGetFoo, setter=intSetFoo:) int intSetterGetter; Save _getter/ _setter
type |= LTEncodingTypePropertyCustomGetter;
if (attrs[i].value) {
_getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
}
} break;
case 'S': {
type |= LTEncodingTypePropertyCustomSetter;
if (attrs[i].value) {
_setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
}
} break;
default: break;
}
}
if (attrs) {
free(attrs);
attrs = NULL;
}
_type = type;
if (_name.length) {
if (!_getter) {
_getter = NSSelectorFromString(_name);
}
if (!_setter) { // if _setter not nil ,set Function name
/**
For Example: name = _title 从t开始,而且t字母表示为大写(uppercaseString用字符串的大写字母标识)(substringToIndex取索引为1的字符,substringFromIndex:取1~~~~(length - 1) 字符)大写T
*/
_setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
}
}
return self;
}
initWithClass/classInfoWithClass方法
- (instancetype)initWithClass:(Class)cls {
// 判断参数的合法性
if (!cls) return nil;
self = [super init];
_cls = cls;
_superCls = class_getSuperclass(cls); // 获得父类
_isMeta = class_isMetaClass(cls); // 是否有元组
if (!_isMeta) {
_metaCls = objc_getMetaClass(class_getName(cls)); // 获得元组
}
_name = NSStringFromClass(cls); // // 获得类名
[self _update]; // 更新方法列表、属性列表、成员列表数据
_superClassInfo = [self.class classInfoWithClass:_superCls]; // 递归最上层父亲信息
return self;
}
+ (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);
/** dispatch_semaphore_create
创建新的计数信号量的初始值。
*/
lock = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
LTClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls)); // 判断该类是元组还是类(initWithClass方法中倒数第二行代码有调用classInfoWithClass方法传入的参数是元组,所以这里需要判断数据存入的是类换成年期还是元组缓存器),根据类名从缓存器中取出数据
if (info && info->_needUpdate) {
[info _update];
}
dispatch_semaphore_signal(lock);
if (!info) { // 缓存器中没有该类信息,创建一个类信息
info = [[LTClassInfo alloc] initWithClass:cls];
if (info) {
// 首先CFDictionaryRef是线程安全,这里加锁目的是为了保证内部数据的线程安全,所有访问CFDictionaryRef接口都要经过这个 lock
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); // 设置信号量为1,相当于添加同步锁
// 缓存classInfo
CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
// 释放锁
dispatch_semaphore_signal(lock);
}
}
return info;
}
+ (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);
/** dispatch_semaphore_create
创建新的计数信号量的初始值。
*/
lock = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
LTClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls)); // 判断该类是元组还是类(initWithClass方法中倒数第二行代码有调用classInfoWithClass方法传入的参数是元组,所以这里需要判断数据存入的是类换成年期还是元组缓存器),根据类名从缓存器中取出数据
if (info && info->_needUpdate) {
[info _update];
}
dispatch_semaphore_signal(lock);
if (!info) { // 缓存器中没有该类信息,创建一个类信息
info = [[LTClassInfo alloc] initWithClass:cls];
if (info) {
// 首先CFDictionaryRef是线程安全,这里加锁目的是为了保证内部数据的线程安全,所有访问CFDictionaryRef接口都要经过这个 lock
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); // 设置信号量为1,相当于添加同步锁
// 缓存classInfo
CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
// 释放锁
dispatch_semaphore_signal(lock);
}
}
return info;
}
-
initWithClass方法将Class相关属性存入YYClassInfo并且内部调用classInfoWithClass方法,在classInfoWithClass方法内部定义了两个缓存器classCache、metaCache,来存储类缓存器、元组缓存器,如果该类存在父类信息,调用initWithClass方法,直至递归最上层父亲信息。
-
为什么使用CFMutableDictionaryRef作为数据存储,而不用NSDictionary?
(1)相对于 Foundation 的方法来说,CoreFoundation 的方法有更高的性能
(2)CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法来遍历容器类能带来不少性能提升,但代码写起来会非常麻烦 -
为什么有元组缓存器?
Model JSON 转换过程中需要很多类的元数据,如果数据足够小,则全部缓存到内存中。 -
这里你肯定有疑问,保存元组的信息,它有什么作用?
根据苹果官方文档描述:objc_msgSend函数的调用过程,有以下描述
1.当调用实例方法时,它会首先在自身isa指针指向的类(class)methodLists中查找该方法,如果找不到则会通过class的super_class指针找到父类的类对象结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,直至根class;
2.当我们调用某个某个类方法时,它会首先通过自己的isa指针找到metaclass,并从其中methodLists中查找该类方法,如果找不到则会通过metaclass的super_class指针找到父类的metaclass对象结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,直至根metaclass;
3.实质这两个存储器作用便于objc_msgSend快速找到该信息,而且是用字典存储的,查找数据时间复杂度为O(1),效率高。
4.防止为了同一个NSObject对象Class解析多次
NSObject+YYModel
/// Foundation Class Type(Foundation框架类的类型)
typedef NS_ENUM (NSUInteger, YYEncodingNSType) {
YYEncodingTypeNSUnknown = 0,
YYEncodingTypeNSString,
YYEncodingTypeNSMutableString,
YYEncodingTypeNSValue,
YYEncodingTypeNSNumber,
YYEncodingTypeNSDecimalNumber,
YYEncodingTypeNSData,
YYEncodingTypeNSMutableData,
YYEncodingTypeNSDate,
YYEncodingTypeNSURL,
YYEncodingTypeNSArray,
YYEncodingTypeNSMutableArray,
YYEncodingTypeNSDictionary,
YYEncodingTypeNSMutableDictionary,
YYEncodingTypeNSSet,
YYEncodingTypeNSMutableSet,
};
/// Get the Foundation class type from property info.根据Class对象获得对应的Foundation类
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;
}
** isKindOfClass、isMemberOfClass、isSubclassOfClass区别**
- (BOOL)isKindOfClass:(Class)aClass;
返回一个布尔值,该值指示class是一个给定的类的实例或者继承自该类的任何类的实例。
如果接收机是当前类实例或者类继承类的实例返回YES,否则返回NO
- (BOOL)isMemberOfClass:(Class)aClass;
返回一个布尔值,该值指示class是否是给定类的实例。
如果接收机是一类的一个实例返回YES,否则返回NO
+ (BOOL)isSubclassOfClass:(Class)aClass;
返回一个布尔值,该值指示class是否是该类的子类,或相同的一个给定的类。
** 为什么用内联函数来判断参数传入的类**
作者:尽量用纯 C 函数、内联函数,使用纯 C 函数可以避免 ObjC 的消息发送带来的开销,如果 C 函数比较小,使用 inline 可以避免一部分压栈弹栈等函数调用的开销。
/// Whether the type is c number.(判断类型编码是否C语言结构的类型)
static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type) {
switch (type & YYEncodingTypeMask) {
case YYEncodingTypeBool:
case YYEncodingTypeInt8:
case YYEncodingTypeUInt8:
case YYEncodingTypeInt16:
case YYEncodingTypeUInt16:
case YYEncodingTypeInt32:
case YYEncodingTypeUInt32:
case YYEncodingTypeInt64:
case YYEncodingTypeUInt64:
case YYEncodingTypeFloat:
case YYEncodingTypeDouble:
case YYEncodingTypeLongDouble: return YES;
default: return NO;
}
}
YYNSNumberCreateFromID方法
Paste_Image.png-
YYEncodingTypeIsCNumber、YYNSNumberCreateFromID、YYNSDateFromString这几个方法作用都是类型转换
-
方法作用:有几种情况,1.传入的对象指向NSNumber类 2.传入的对象指向NSString类,最终的会转换返回NSNumber ,针对传入的对象指向NSString类的进行处理,其处理过程又分为两种情况,第一种情况是传入的值是true、false、yes、no、nil等格式1数据,另外一种是传入的值是5788.57格式2数据,具体处理详解见下面源码,当然还有可能传入的5788就不做处理直接返回了 。
-
为什么要进行处理?
(1)当 JSON/Dictionary 中的对象类型与 Model 属性不一致时,YYModel 将会进行如下自动转换。自动转换不支持的值将会被忽略,以避免各种潜在的崩溃问题。
(2)作者关于类型转换的描述是这样的,是之前在项目中遇到了这样一些情况:本来服务端给的参数是一个 number 类型,经过模型转换后,赋值到一个 NSNumber 的属性中去,但后来服务端不小心改掉了,换成了 string 类型,造成后续访问那个 NSNumber 属性时,实际访问的是 NSString ,然后造成了一些崩溃。开发时有些地方可能漏掉了类型检查,所以希望模型转换时,能更加自动一些,尽量避免各种崩溃问题。
三种格式
- 格式1、True、FALSE、YES、no、NULL、nil、Null、YES...
- 格式2、5788
- 格式3、5788.57
/// Parse a number value from 'id'. (1)id类型对象 -》 NSNumber对象 (2) id类型对象 -》NSString类型对象 -》NSNumber类型对象
static force_inline NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value) {
static NSCharacterSet *dot;
static NSDictionary *dic;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ // 传入的值属性格式1对其进行处理
dot = [NSCharacterSet characterSetWithRange:NSMakeRange('.', 1)];
// KEY-Value
dic = @{@"TRUE" : @(YES),
@"True" : @(YES),
@"true" : @(YES),
@"FALSE" : @(NO),
@"False" : @(NO),
@"false" : @(NO),
@"YES" : @(YES),
@"Yes" : @(YES),
@"yes" : @(YES),
@"NO" : @(NO),
@"No" : @(NO),
@"no" : @(NO),
@"NIL" : (id)kCFNull,
@"Nil" : (id)kCFNull,
@"nil" : (id)kCFNull,
@"NULL" : (id)kCFNull,
@"Null" : (id)kCFNull,
@"null" : (id)kCFNull,
@"(NULL)" : (id)kCFNull,
@"(Null)" : (id)kCFNull,
@"(null)" : (id)kCFNull,
@"<NULL>" : (id)kCFNull,
@"<Null>" : (id)kCFNull,
@"<null>" : (id)kCFNull};
});
// 判断参数属于格式1,直接返回nil
if (!value || value == (id)kCFNull) return nil;
// 判断参数类型是否标识数字类型NSNumber,属于格式2,无需处理直接返回值
if ([value isKindOfClass:[NSNumber class]]) return value;
// 判断参数是表示字符串NSString
if ([value isKindOfClass:[NSString class]]) {
NSNumber *num = dic[value];
if (num) {
if (num == (id)kCFNull) return nil;
return num;
}
// 如果传入的value值是5788.57, rangeOfCharacterFromSet查找字符串是否包含 `.`字符
if ([(NSString *)value rangeOfCharacterFromSet:dot].location != NSNotFound) {
const char *cstring = ((NSString *)value).UTF8String; // 因为要调用atof方法所以将value的类型转换为UTF-8类型
if (!cstring) return nil;
double num = atof(cstring); // double atof(const char *nptr); 将数字从NSString转化为double
if (isnan(num) || isinf(num)) return nil;
return @(num);
} else { // value传入的值是5788转换为NSNumber数字类型
const char *cstring = ((NSString *)value).UTF8String;
if (!cstring) return nil;
return @(atoll(cstring)); // long long atoll(const char *nptr); 把字符串转换成长长整型数(64位)
}
}
return nil;
}
- 为什么使用__unsafe_unretained?
作者回答:在 ARC 条件下,默认声明的对象是 __strong 类型的,赋值时有可能会产生 retain/release 调用,如果一个变量在其生命周期内不会被释放,则使用 __unsafe_unretained 会节省很大的开销。
网友提问: 楼主的偏好是说用__unsafe_unretained来代替__weak的使用,使用后自行解决野指针的问题吗?
作者回答:关于 __unsafe_unretained 这个属性,我只提到需要在性能优化时才需要尝试使用,平时开发自然是不推荐用的。
我的理解:貌似没有回答会不会自行解决野指针的问题,我的理解是使用__unsafe_unretained是否会出现野指针,根据使用环境进行分析,如果能保证所指的对象不为nil就不会出现野指针,例如在ModelSetValueForProperty传入value对其进行�if (value == (id)kCFNull) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);,如果value参数为空自然不会调用LTNSDateFromString方法,而且ModelSetValueForProperty这个方法定义的是static静态方法,变量在其生命周期内不会被释放,所以当方法执行完成后不会将该方法里的对象放入到autoreleasePool导致value被释放,在后续ModelSetValueForProperty方法中也没有对value = nil 操作,所以这里不会导致野指针的出现。
**YYNSDateFromString方法 **
/// Parse string to date.
static force_inline NSDate *YYNSDateFromString(__unsafe_unretained NSString *string) {
typedef NSDate* (^YYNSDateParseBlock)(NSString *string);
#define kParserNum 34 // 日期字符串的最大长度,实际
// 保存对应长度长度日期字符串解析的Block数组,设置数组内值都为0,使用静态来保存解析成NSDate的Block并且设置对应日期字符串长度
static YYNSDateParseBlock blocks[kParserNum + 1] = {0};
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
{
/*
2014-01-20 // Google
*/
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter.dateFormat = @"yyyy-MM-dd";
// 日期的字符串长度为10
blocks[10] = ^(NSString *string) { return [formatter dateFromString:string]; };
}
{
/*
格式1:2014-01-20 12:24:48
格式2:2014-01-20T12:24:48 // Google
格式3:2014-01-20 12:24:48.000
格式4:2014-01-20T12:24:48.000
*/
/** 长度为19的日期字符串解析,分两种
*/
NSDateFormatter *formatter1 = [[NSDateFormatter alloc] init];
formatter1.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter1.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter1.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss";
NSDateFormatter *formatter2 = [[NSDateFormatter alloc] init];
formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter2.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter2.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSDateFormatter *formatter3 = [[NSDateFormatter alloc] init];
formatter3.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter3.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter3.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS";
NSDateFormatter *formatter4 = [[NSDateFormatter alloc] init];
formatter4.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter4.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter4.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS";
// 将日期字符串解析的Block保存到19的数组位置
blocks[19] = ^(NSString *string) {
if ([string characterAtIndex:10] == 'T') { // 格式2
return [formatter1 dateFromString:string];
} else { // 格式1
return [formatter2 dateFromString:string];
}
};
// 将日期字符串解析的Block保存到23的数组位置
blocks[23] = ^(NSString *string) {
if ([string characterAtIndex:10] == 'T') { // 格式3
return [formatter3 dateFromString:string];
} else { // 格式4
return [formatter4 dateFromString:string];
}
};
}
{
/*
格式1:2014-01-20T12:24:48Z // Github, Apple
格式2:2014-01-20T12:24:48+0800 // Facebook
格式3:2014-01-20T12:24:48+12:00 // Google
格式4:2014-01-20T12:24:48.000Z
格式5:2014-01-20T12:24:48.000+0800
格式6:2014-01-20T12:24:48.000+12:00
*/
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
NSDateFormatter *formatter2 = [NSDateFormatter new];
formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter2.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ";
// 将日期字符串解析的Block保存到20的数组位置,格式1
blocks[20] = ^(NSString *string) { return [formatter dateFromString:string]; };
// 将日期字符串解析的Block保存到24的数组位置,格式2
blocks[24] = ^(NSString *string) { return [formatter dateFromString:string]?: [formatter2 dateFromString:string]; };
// 将日期字符串解析的Block保存到25的数组位置,格式3
blocks[25] = ^(NSString *string) { return [formatter dateFromString:string]; };
// 将日期字符串解析的Block保存到28的数组位置,格式4
blocks[28] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
// 将日期字符串解析的Block保存到29的数组位置,格式5
blocks[29] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
}
{
/*
格斯1:Fri Sep 04 00:12:21 +0800 2015 // Weibo, Twitter
格式2:Fri Sep 04 00:12:21.000 +0800 2015
*/
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy";
NSDateFormatter *formatter2 = [NSDateFormatter new];
formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter2.dateFormat = @"EEE MMM dd HH:mm:ss.SSS Z yyyy";
// 将日期字符串解析的Block保存到30的数组位置,格式1
blocks[30] = ^(NSString *string) { return [formatter dateFromString:string]; };
// 将日期字符串解析的Block保存到34的数组位置,格式2
blocks[34] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
}
});
// 判断传入值合法性
if (!string) return nil;
// 如果传入字符串长度越界,不属于转换的范围内,直接返回nil
if (string.length > kParserNum) return nil;
// 设置block数组大小
YYNSDateParseBlock parser = blocks[string.length];
if (!parser) return nil;
return parser(string);
#undef kParserNum
}
**YYNSBlockClass方法 **
/// Get the 'NSBlock' class.获得NSBlock类
static force_inline Class YYNSBlockClass() {
static Class cls;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
void (^block)(void) = ^{};
cls = ((NSObject *)block).class; // 获得当前Block的Class
while (class_getSuperclass(cls) != [NSObject class]) { // 遍历Block(__NSGlobalBlock__ - > __NSGlobalBlock - > NSBlock - > NSObject)最终的superClass,NSBlock的父类是NSObject
cls = class_getSuperclass(cls);
}
});
return cls; // current is "NSBlock"
}
**YYISODateFormatter **
/**
Get the ISO date formatter. 获得ISO日期格式
ISO:国际标准化组织的国际标准ISO 8601是日期和时间的表示方法
ISO8601 format example:
2010-07-09T16:13:30+12:00
2011-01-11T11:11:11+0000
2011-01-26T19:06:43Z
length: 20/24/25
*/
static force_inline NSDateFormatter *YYISODateFormatter() {
static NSDateFormatter *formatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 初始化formatter
formatter = [[NSDateFormatter alloc] init];
// 设置时区
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
// 设置ISO日期格式
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
});
return formatter;
}
YYValueForKeyPath/YYValueForMultiKeys方法
/// Get the value with key paths from dictionary(从字典中获取关键路径的值)
/// The dic should be NSDictionary, and the keyPath should not be nil. (dic[keyPaths[i]],应该是NSDictionaty字典,否则返回nil。)
/**
json格式见下面方法
*/
static force_inline id LTValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) {
id value = nil;
for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) {
/** 这里的keyPaths可以这么理解,如果传入的是以下JSON数据,那么keypath.count为2 此时max = 2
1. i = 0 , 0 = i < 2 = max; 2. url = keyPaths[0] = keyPaths[i]
3. http://example.com/1.png = value = dic[@"url"], 如果value不是NSDictionary类型直接返回
4. 0 + 1 = i + 1 < 2 = max
5. i++,
--------------------------
1. i = 1, 1 = i < 2 = max; 2. desc = keyPath[1] = keyPaths[i]
3. Happy~ = value = dic[@"desc"], 如果value不是NSDictionary类型直接返回
4. 1 + 1 < i + 1 < 2 = max 返回该值
*/
/**
格式2 "photos" : [ 格式2
{
"url":"http://example.com/1.png\",
"desc":"Happy~"
},
{
"url":"http://example.com/2.png\",
"desc":"Yeah!"
}
]
*/
value = dic[keyPaths[i]]; // 根据key取值,keypath
if (i + 1 < max) { // 相当于NSArray数组倒数第二个key
// 判断该值如果是字典,直接赋值,否则返回Nil,格式2内的格式6
if ([value isKindOfClass:[NSDictionary class]]) {
dic = value; //
} else {
return nil;
}
}
}
return value;
}
/// Get the value with multi key (or key path) from dictionary(从字典中获取多个键(或KeyPath值相当于字典)的值)
/// The dic should be NSDictionary(dic是一个字典)
/** json
{
"name" : "Happy Birthday", 格式1
"photos" : [
{
"url":"http://example.com/1.png",
"desc":"Happy~"
},
{
"url":"http://example.com/2.png",
"desc":"Yeah!"
}
],
"likedUsers" : { 格式1
"Jony" : {"uid":10001,"name":"Jony"}, 格式3
"Anna" : {"uid":10002,"name":"Anna"}, 格式3
"desc":"Happy~", 格式4
格式2 "photos" : [ 格式6
{
"url":"http://example.com/1.png",
"desc":"Happy~"
},
{
"url":"http://example.com/2.png", 格式5
"desc":"Yeah!"
}
]
},
"likedUserIds" : [10001,10002]
}
*/
static force_inline id LTValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) {// dic 为格式1
id value = nil;
for (NSString *key in multiKeys) { // 遍历multiKeys数组,依次取出key
if ([key isKindOfClass:[NSString class]]) { // 如果是key字符类型,根据key从dic直接取出数据,格式1内存在格式4
value = dic[key];
if (value) break; // 如果根据key取出对应的值,停止继续取值
} else { // 根据Key的值如果NSSArray,进入LTValueForKeyPath取出指定key的值,例如格式1内存在格式2
value = LTValueForKeyPath(dic, (NSArray *)key);
// 如果根据key取出对应的值,停止继续取值
if (value) break;
}
}
return value;
}
_YYModelPropertyMeta
Paste_Image.png Paste_Image.png@interface _YYModelPropertyMeta : NSObject {
@package
NSString *_name; ///< property's name 属性名称
// 对象类型
YYEncodingType _type; ///< property's type 属性的基础类型
// 对象类型
YYEncodingNSType _nsType; ///< property's Foundation type 属性的 Foundation Class类型
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为nil,自定义类
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
BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver 结构是否支持 archiver(归档)/unarchiver(解档)
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 to key
// 如果有多级映射 如果有多个属性映射到相同的键会用到
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
_YYModelPropertyMeta属声明属性详细解析
- _name: 属性的名称
- _type: 属性对应的基础类型编码
- _nsType: Foundation框架类型编码
- _isCNumber:判断是否C语言结构类型
- _cls:实例变量来源于哪个类
- _genericCls:如下例子,genericCls = LMBook,该值用来判断是否自定义映射,如果是自定义映射则值为自定义映射的当前类
@impleentation LMBook
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"name" : @"n",
@"page" : @"p",
@"desc" : @"ext.desc",
@"bookID": @[@"id", @"ID", @"book_id"]};
}
@end
- _getter:保存属性的getter方法
- _setter:保存属性的setter方法
- _isKVCCompatible:判断是否支持KVC
- _isStructAvailableForKeyedArchiver:判断结构是否支持 archiver(归档)/unarchiver(解档) 编码
- _hasCustomClassFromDictionary:判断是否存在自定义映射的字典
- _mappedToKey:简单映射Key->Value,例如以下情况
@{@"name" : @"n",
@"page" : @"p"}
- _mappedToKeyPath:稍微复杂映射,例如以下情况格式1
json:
{
"n":"Harry Pottery",
"p": 256,
格式1"ext" : {
"desc" : "A book written by J.K.Rowling."
},
"ID" : 100010
}
- _mappedToKeyArray:实例属性Key映射多个不同的Json key,例如格式1情况
@{@"name" : @"n",
@"page" : @"p",
@"desc" : @"ext.desc",
格式1 @"bookID": @[@"id", @"ID", @"book_id"]
metaWithClassInfo方法
// 根据YYClassInfo信息初始化YYClassInfo并且设置对应属性信息
+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
_YYModelPropertyMeta *meta = [self new];
meta->_name = propertyInfo.name;
meta->_type = propertyInfo.type;
meta->_info = propertyInfo;
meta->_genericCls = generic;
// 如果属性的类型编码是对象,例如id, NSDate...,如果是对象类型那么一般是Foundation类型,直接根据属性的属性的类信息传入当前属性class信息或者对应Foundation类型
if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) { // 属性的 Foundation Class类型
meta->_nsType = YYClassGetNSType(propertyInfo.cls);
} else { // 属性是c语言基础类型
meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type);
}
if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) { // 属性是结构体类型
/*
It seems that NSKeyedUnarchiver cannot decode NSValue except these structs:
iOS能够归档的struct类型有限制,能够使用归档的struct类型type encodings为如下:
32 bit struct类型的 @encode()
*/
static NSSet *types = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableSet *set = [NSMutableSet new];
// 32 bit
[set addObject:@"{CGSize=ff}"];
[set addObject:@"{CGPoint=ff}"];
[set addObject:@"{CGRect={CGPoint=ff}{CGSize=ff}}"];
[set addObject:@"{CGAffineTransform=ffffff}"];
[set addObject:@"{UIEdgeInsets=ffff}"];
[set addObject:@"{UIOffset=ff}"];
/**
iOS能够归档的struct类型有限制,能够使用归档的struct类型type encodings为如下:
64 bit struct类型的 @encode()
*/
// 64 bit
[set addObject:@"{CGSize=dd}"];
[set addObject:@"{CGPoint=dd}"];
[set addObject:@"{CGRect={CGPoint=dd}{CGSize=dd}}"];
[set addObject:@"{CGAffineTransform=dddddd}"];
[set addObject:@"{UIEdgeInsets=dddd}"];
[set addObject:@"{UIOffset=dd}"];
types = set;
});
if ([types containsObject:propertyInfo.typeEncoding]) {
meta->_isStructAvailableForKeyedArchiver = YES;
}
}
meta->_cls = propertyInfo.cls;
if (generic) { // 如果存在自定义映射
meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)]; // 从传入的generic class读取自定义映射,设置该代理方法为第一响应
} else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) { // 属性类名不为空且不是Foundation类型
meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)]; // 从属性变量类型的Class读取自定义映射,并且设置该代理方法为第一响应
}
// 保存Property的getter
if (propertyInfo.getter) {
/**
instancesRespondToSelector:被调用时,动态方法是有机会的首先为selector提供一个IMP,如果该类对应的Property有getter方法实现方法,则返回YES
*/
if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
meta->_getter = propertyInfo.getter; // 从属性列表获取响应属性的getter方法
}
}
// 保存Property的setter
if (propertyInfo.setter) {
/**
instancesRespondToSelector:被调用时,动态方法是有机会的首先为selector提供一个IMP,如果该类对应的Property有setter方法实现方法,则返回YES
*/
if ([classInfo.cls instancesRespondToSelector:propertyInfo.setter]) {
meta->_setter = propertyInfo.setter;
}
}
/** 属性变量是否支持KVC,有一个条件
* getter/setter方法必须实现
*/
if (meta->_getter && meta->_setter) {
/*
KVC invalid type:
long double
pointer (such as SEL/CoreFoundation object)
*/
/** 有两种类型不支持KVC
* 1.long double 不支持KVC
* 2. pointer (such as SEL/CoreFoundation object) 不支持KVC
*/
switch (meta->_type & YYEncodingTypeMask) {
case YYEncodingTypeBool:
case YYEncodingTypeInt8:
case YYEncodingTypeUInt8:
case YYEncodingTypeInt16:
case YYEncodingTypeUInt16:
case YYEncodingTypeInt32:
case YYEncodingTypeUInt32:
case YYEncodingTypeInt64:
case YYEncodingTypeUInt64:
case YYEncodingTypeFloat:
case YYEncodingTypeDouble:
case YYEncodingTypeObject:
case YYEncodingTypeClass:
case YYEncodingTypeBlock:
case YYEncodingTypeStruct:
case YYEncodingTypeUnion: {
meta->_isKVCCompatible = YES;
} break;
default: break;
}
}
return meta;
}
网友评论
有个疑问,刚开始看源码,发现大部分用Core Foundation框架里的数据类型,例如CFMutableDictionaryRef等,想问下这只是个人的编码习惯,还是CF框架有自己的优势,看到很多地方都要__bridge来桥接对象,感觉挺麻烦的,所以很纳闷为什么很多第三方框架喜欢这么写?希望作者可以解答一下,万分感谢~
1.相对于 Foundation 的方法来说,CoreFoundation 的方法有更高的性能
2.同时YYModel源码中你还可以看到使用了CFArrayApplyFunction、CFDictionaryApplyFunction两个方法,当解析字段过多、嵌套复杂时,这两个方法配套使用能带来不少性能提升,这也是为什么很多第三方框架喜欢这么写,具体性能提升可以查阅(http://ridiculousfish.com/blog/posts/array.html)得知,只是代码写起来比较麻烦.
3.更进一步其实从自我提升来说的话CoreFoundation框架优势就是开源,实现细节可以从(https://github.com/apple/swift-corelibs-foundation/blob/173e9ea16cf1a0ed9b21cc11ea8a2d15b3f8dca9/CoreFoundation/Collections.subproj)看到,从中得知CFMutableArray是由hash table或binary tree数据结构构建的,CFMutableDictionary:是由hash table或binary tree、bit_vector位向量容器来构建的,当你想构建一个存储结构时,这些都是非常有用的资料