接上文,本文主要针对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];
|
是否是元类?
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"
|
获取元类对象
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 13
| _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 37
| //假如调用对象是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方法,可以进一步参考。
系列
- 解码YYModel(一)基础
- 解码YYModel(二)特性
- 解码YYModel(三)参考