解码YYModel(三)参考

接上文,本文主要针对YYModel中一些使用的runtime的方法以及其他代码片段提供一个指南:

继续使用上文的类YYMessage,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@interface YYMessage : NSObject
@property (nonatomic, assign) uint64_t messageId;
@property (nonatomic, strong) NSString *content;
@property (nonatomic, strong) NSDate *time;
@property (nonatomic ,copy) NSString *name;
@end

@implementation YYMessage
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"messageId":@[@"id", @"ID", @"mes_id"],
@"time":@"t",
@"name":@"user.name"
};
}
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic {
uint64_t timestamp = [dic unsignedLongLongValueForKey:@"t" default:0];
self.time = [NSDate dateWithTimeIntervalSince1970:timestamp / 1000.0];
return YES;
}
- (void)modelCustomTransformToDictionary:(NSMutableDictionary *)dic {
dic[@"t"] = @([self.time timeIntervalSince1970] * 1000).description;
}
@end

Class

class

类对象是什么?

YYMessage instance class is YYMessage

1
Class cls = [self class];

class_isMetaClass

是否是元类?

YYMessage instance isn’t meta class

1
_isMeta = class_isMetaClass(cls);		//_isMeta = NO;

class_getName

获取类名

YYMessage instance name is “YYMessage”

1
_name = class_getName(cls);		//_name is "YYMessage"

objc_getMetaClass

获取元类对象

YYMessage instance metaCls is YYMessage

1
_metaCls = objc_getMetaClass(class_getName(cls));	//_metaCls is YYMessage

class_getSuperclass

获取父类

YYMessage super class is NSObject

1
_superCls = class_getSuperclass(cls);	//_superCls is NSObject

Method

消息

class_copyMethodList

获取消息数组

1
2
3
4
   //methods是method数组
//methodCount = 11
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(cls, &methodCount);

以下几个方法是基于该方法的取值:

​ - (void)setMessageId:(uint64_t)messageId;

method_getName

获取方法名

1
2
//(SEL) _sel = "setMessageId:"
_sel = method_getName(method);

method_getImplementation

获取方法SEL

1
2
//(IMP) _imp = 0x00000001054d6210 (YYKitDemo`-[YYMessage setMessageId:] at YYModelExample.m:133)
_imp = method_getImplementation(method);

sel_getName

获取方法SEL名

1
2
//name = "modelCustomTransformFromDictionary:"
const char *name = sel_getName(_sel);

method_getTypeEncoding

获取方法的type encoding

1
2
3
//- (voide)setMessageId:(uint64_t)messageID
//typeEncoding = v24@0:8Q16
const char *typeEncoding = method_getTypeEncoding(method);

method_copyReturnType

获取方法返回值的 type encoding

1
2
3
//- (voide)setMessageId:(uint64_t)messageID
//_returnTypeEncoding = "v"
_returnTypeEncoding = [NSString stringWithUTF8String:returnType];

method_getNumberOfArguments

获取方法调用的参数个数

1
2
3
4
   //- (voide)setMessageId:(uint64_t)messageID
// argumentCount = 3
// self、sel、messageID
unsigned int argumentCount = method_getNumberOfArguments(method);

method_copyArgumentType

参考:构建iOS-Model层(二)类型解析

1
2
3
4
5
   //- (voide)setMessageId:(uint64_t)messageID
// self 即id类型,argumentType = @“@”
// sel类型,argumentType = @“:”
//uint64_t类型,argumentType = @“Q”
char *argumentType = method_copyArgumentType(method, i);

Property

属性

class_copyPropertyList

获取属性数组

1
2
3
4
   // properties 属性数组
// propertyCount = 4
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);

以下是基于该属性的取值:

​ @property (nonatomic, assign) uint64_t messageId;

property_getName

获取属性名

1
2
//name = "messageId"
const char *name = property_getName(property);

property_copyAttributeList

获取属性attribute 数组

1
2
3
// attrCount = 3,
unsigned int attrCount;
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
name value 说明
attrs[0] “T” “Q” Specifies the type using old-style encoding
attrs[1] “N” “” nonatomic
attrs[2] “V” _messageId” 实例变量

Ivar

实例变量

class_copyIvarList

获取实例变量数组

1
2
3
4
 	// ivarCount = 4
// ivars 是实例变量的数组
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(cls, &ivarCount);

以下基于_messageId的取值:

ivar_getName

获取变量名:

1
2
   //name = "_messageId"
const char *name = ivar_getName(ivar);

ivar_getOffset

获取变量在类对象中的偏移位置:

1
2
3
4
5
6

//标准库类型(library type)ptrdiff_t 与 size_t 类型一样,ptrdiff_t 也是一种与机器相关的类型,在 cstddef 头文件中定义。size_t 是unsigned 类型,而 ptrdiff_t 则是 signed 整型
// _offset = 8
ptrdiff_t _offset = ivar_getOffset(ivar);

//假如是_content,则会_offset = 16

ivar_getTypeEncoding

获取变量的type encoding

1
2
3
4
   //typeEncoding = "Q"
const char *typeEncoding = ivar_getTypeEncoding(ivar);

//假如是_content,则对应 typeEncoding = @“NSString”

NSObject

NSObject是个有意思的类,我们通过上面的分析,来看看它的一些特性:

1
2
3
4
5
6
7
8
9
10
11
12
_cls: NSObject
_superCls: nil
_metaCls: NSObject
_isMeta: NO
methodCount: 1328
propertyCount: 49
ivarCount: 1

ivar: 只有一个实例变量,isa
> isa
typeEncoding: # (说明是Class类型)
offset: 0

NSDate解析

YYModel中关于string解析为date,使用了block,是个很巧妙的用法。

​ 下面是支持的格式:

格式 示例
yyyy-MM-dd 2014-01-20
yyyy-MM-dd HH:mm:ss 2014-01-20 12:24:48/ 2014-01-20T12:24:48.000
yyyy-MM-dd’T’HH:mm:ss 2014-01-20T12:24:48/2014-01-20T12:24:48.000
yyyy-MM-dd’T’HH:mm:ssZ 2014-01-20T12:24:48Z/ 2014-01-20T12:24:48+0800/ 2014-01-20T12:24:48+12:00
yyyy-MM-dd’T’HH:mm:ss.SSSZ 2014-01-20T12:24:48.000+0800/ 2014-01-20T12:24:48.000+12:00/ 2014-01-20T12:24:48.000Z
EEE MMM dd HH:mm:ss Z yyyy Fri Sep 04 00:12:21 +0800 2015
EEE MMM dd HH:mm:ss.SSS Z yyyy Fri Sep 04 00:12:21.000 +0800 2015

​ 拿到上面格式列表,我们很容易想到,读取String length,然后作一堆的if/else。我们在上一篇分析到,if/else是个低效的遍历情况的情形。我们应该要避免。

​ 看看YYModel如何避免的,简化的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/// Parse string to date.
static force_inline NSDate *YYNSDateFromString(__unsafe_unretained NSString *string) {
typedef NSDate* (^YYNSDateParseBlock)(NSString *string);
#define kParserNum 34
static YYNSDateParseBlock blocks[kParserNum + 1] = {0};
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter.dateFormat = @"yyyy-MM-dd";
blocks[10] = ^(NSString *string) { return [formatter dateFromString:string]; };
}

{
formatter1.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss";
formatter2.dateFormat = @"yyyy-MM-dd HH:mm:ss";
formatter3.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS";
formatter4.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS";

blocks[19] = ^(NSString *string) {
if ([string characterAtIndex:10] == 'T') {
return [formatter1 dateFromString:string];
} else {
return [formatter2 dateFromString:string];
}
};

blocks[23] = ......
}
{
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
formatter2.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ";
blocks[20] = .....
blocks[24] = .....
blocks[25] = .....
blocks[28] = .....
blocks[29] = .....
}

{
formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy";
formatter2.dateFormat = @"EEE MMM dd HH:mm:ss.SSS Z yyyy";
blocks[30] = .....
blocks[34] = .....
}
});
if (!string) return nil;
if (string.length > kParserNum) return nil;
YYNSDateParseBlock parser = blocks[string.length];
if (!parser) return nil;
return parser(string);
#undef kParserNum
}

​ 我们看到,在做转化前,映射了length<->block,只要拿到string length,传到block,就能返回NSDate对象。

​ length<->block的mapper关系,直接将我们需要条件分支判断,变成了查表,效率得到提升。

copy

​ 为了实现copy协议,YYModel中对类型的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//假如调用对象是Foundation对象,那么采用系统默认的copy协议
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class];
if (modelMeta->_nsType) return [self copy];

//否则不是Foundation对象,即自定义对象,则需要对每个属性的类型进行分类处理
if (propertyMeta->_isCNumber) {
case YYEncodingTypeBool: {
bool num = ((bool (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
} break;
} else {
switch (propertyMeta->_type & YYEncodingTypeMask) {
case YYEncodingTypeObject:
case YYEncodingTypeClass:
case YYEncodingTypeBlock: {
id value = ((id (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)one, propertyMeta->_setter, value);
} break;
case YYEncodingTypeSEL:
case YYEncodingTypePointer:
case YYEncodingTypeCString: {
size_t value = ((size_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
((void (*)(id, SEL, size_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, value);
} break;
case YYEncodingTypeStruct:
case YYEncodingTypeUnion: {
@try {
NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];
if (value) {
[one setValue:value forKey:propertyMeta->_name];
}
} @catch (NSException *exception) {}
} // break; commented for code coverage in next line
default: break;
}
}

​ 上面除了C 数字类型,直接从getter中获取然后setter,类型也是property一致的。

1
2
//假如property是bool类型,那么getter返回也是bool	
bool num = ((bool (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);

​ 除此,需要区分三个类型,分别是:

1
2
3
4
5
6
7
8
//id、Class、Block类型
id value = ((id (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);

//void*、char*、SEL
size_t value = ((size_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);

//struct、union
NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];

其他

YYModel实现了NSCoding协议,实现了hash以及equal方法,可以进一步参考。

系列

  1. 解码YYModel(一)基础
  2. 解码YYModel(二)特性
  3. 解码YYModel(三)参考