原文发布于:wenghengcong.com
接上文,本文主要针对YYModel中一些使用的runtime的方法以及其他代码片段提供一个指南:
继续使用上文的类YYMessage,如下:
@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
Class cls = [self class];
class_isMetaClass
是否是元类?
YYMessage
instance isn't meta class
_isMeta = class_isMetaClass(cls); //_isMeta = NO;
class_getName
获取类名
YYMessage
instance name is "YYMessage"
_name = class_getName(cls); //_name is "YYMessage"
objc_getMetaClass
获取元类对象
YYMessage
instance metaCls is YYMessage
_metaCls = objc_getMetaClass(class_getName(cls)); //_metaCls is YYMessage
class_getSuperclass
获取父类
YYMessage
super class is NSObject
_superCls = class_getSuperclass(cls); //_superCls is NSObject
Method
消息
class_copyMethodList
获取消息数组
//methods是method数组
//methodCount = 11
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(cls, &methodCount);
以下几个方法是基于该方法的取值:
- (void)setMessageId:(uint64_t)messageId;
method_getName
获取方法名
//(SEL) _sel = "setMessageId:"
_sel = method_getName(method);
method_getImplementation
获取方法SEL
//(IMP) _imp = 0x00000001054d6210 (YYKitDemo`-[YYMessage setMessageId:] at YYModelExample.m:133)
_imp = method_getImplementation(method);
sel_getName
获取方法SEL名
//name = "modelCustomTransformFromDictionary:"
const char *name = sel_getName(_sel);
method_getTypeEncoding
获取方法的type encoding
//- (voide)setMessageId:(uint64_t)messageID
//typeEncoding = v24@0:8Q16
const char *typeEncoding = method_getTypeEncoding(method);
method_copyReturnType
获取方法返回值的 type encoding
//- (voide)setMessageId:(uint64_t)messageID
//_returnTypeEncoding = "v"
_returnTypeEncoding = [NSString stringWithUTF8String:returnType];
method_getNumberOfArguments
获取方法调用的参数个数
//- (voide)setMessageId:(uint64_t)messageID
// argumentCount = 3
// self、sel、messageID
unsigned int argumentCount = method_getNumberOfArguments(method);
method_copyArgumentType
参考:构建iOS-Model层(二)类型解析
//- (voide)setMessageId:(uint64_t)messageID
// self 即id类型,argumentType = @“@”
// sel类型,argumentType = @“:”
//uint64_t类型,argumentType = @“Q”
char *argumentType = method_copyArgumentType(method, i);
Property
属性
class_copyPropertyList
获取属性数组
// properties 属性数组
// propertyCount = 4
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
以下是基于该属性的取值:
@property (nonatomic, assign) uint64_t messageId;
property_getName
获取属性名
//name = "messageId"
const char *name = property_getName(property);
property_copyAttributeList
获取属性attribute 数组
// 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
获取实例变量数组
// ivarCount = 4
// ivars 是实例变量的数组
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(cls, &ivarCount);
以下基于_messageId的取值:
ivar_getName
获取变量名:
//name = "_messageId"
const char *name = ivar_getName(ivar);
ivar_getOffset
获取变量在类对象中的偏移位置:
//标准库类型(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
//typeEncoding = "Q"
const char *typeEncoding = ivar_getTypeEncoding(ivar);
//假如是_content,则对应 typeEncoding = @“NSString”
NSObject
NSObject
是个有意思的类,我们通过上面的分析,来看看它的一些特性:
_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
如何避免的,简化的代码如下:
/// 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
中对类型的处理:
//假如调用对象是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一致的。
//假如property是bool类型,那么getter返回也是bool
bool num = ((bool (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
除此,需要区分三个类型,分别是:
//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阅读摘要(三)参考