解码YYModel(三)参考

320 阅读5分钟

接上文,本文主要针对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 ](/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);
namevalue说明
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-dd2014-01-20
yyyy-MM-dd HH:mm:ss2014-01-20 12:24:48/ 2014-01-20T12:24:48.000
yyyy-MM-dd'T'HH:mm:ss2014-01-20T12:24:48/2014-01-20T12:24:48.000
yyyy-MM-dd'T'HH:mm:ssZ2014-01-20T12:24:48Z/ 2014-01-20T12:24:48+0800/ 2014-01-20T12:24:48+12:00
yyyy-MM-dd'T'HH:mm:ss.SSSZ2014-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 yyyyFri Sep 04 00:12:21 +0800 2015
EEE MMM dd HH:mm:ss.SSS Z yyyyFri 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方法,可以进一步参考。

系列

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