OC的run-time 机制,简直像是网络上的猫! 我在开发中很少用到,但是作为iOS开发 人家肯定会问这个东西,所以深入的学习了下。
对于 run-time的入手,YYModel的学习,简直让人美滋滋。 对于YYModel的 源码解析什么的,网上很多,我主要参考 马在路上 的总结,他都这个总结,是我看过对于新手最友好的总结。浅显易懂,逐条分析,流程分析。很到位!
我呢,就记录下一下我是如何一步步学习YYModel的。
先从YYClassInfo.h开始看下图1-1-1,
图 1-1-1
先说这个 NS_ASSUME_NONNULL_BEGIN 这个宏定义, 要和 NS_ASSUME_NONNULL_END 配对使用。表示在这两个宏之间声明的属性默认是nonnull的。当然还有 NS_ASSUME_NULLABLE_BEGIN 和 NS_ASSUME_NONNULL_END.
具体 参考《Object-C中的黑魔法》。
再看这个 typedef ,这是一个枚举类型,是一个位移枚举,而它(NS_OPTIONS)和通用枚举(NS_ENUM)的区别在于 位移枚举可以在你需要的地方有多个选项,而通用枚举只能存在一个。
typedef NS_OPTIONS(NSUInteger, YYEncodingType){ YYEncodingTypeMask = 0xFF, // 值类型的mask YYEncodingTypeUnknow = 0, YYEncodingTypeVoid = 1, YYEncodingTypeBool = 2, YYEncodingTypeInt8 = 3, YYEncodingTypeUInt8 = 4, YYEncodingTypeInt16 = 5, YYEncodingTypeUInt16 = 6, YYEncodingTypeInt32 = 7, YYEncodingTypeUInt32 = 8, YYEncodingTypeInt64 = 9, YYEncodingTypeUInt64 = 10, YYEncodingTypeFloat = 11, YYEncodingTypeDouble = 12, YYEncodingTypeLongDoubel = 13, YYEncodingTypeObject = 14, YYEncodingTypeClass = 15, YYEncodingTypeSEL = 16, YYEncodingTypeBlock = 17, YYEncodingTypePointer = 18, YYEncodingTypeStruct = 19, YYEncodingTypeUnion = 20,// 共用体,内存长度为内存对齐方式(可以暂时理解为以成员变量最长的为准),和结构体类似,但结构体长度为成员变量的总和。它是以覆盖的方式储存在同一内存段中,所以,只能读取最后添加的成员变量 YYEncodingTypeCString = 21, YYEncodingTypeCArray = 22, YYEncodingTypeQualifierMask = 0xFF00,//限定符的mask YYEncodingTypeQualifierConst = 1 << 8, YYEncodingTypeQualifierIn = 1 << 9 , YYEncodingTypeQualifierInOut = 1 << 10, //swift中通过一个函数改变函数外面变量的值(将一个值类型参数以引用方式传递), YYEncodingTypeQualifierOut = 1 << 11, YYEncodingTypeQualifierBycopy = 1 << 12, YYEncodingTypeQualifierByref = 1 << 13, YYEncodingTypeQualifierOneway = 1 << 14, YYEncodingTypePropertyMask = 0xFF0000, //属性的mask YYEncodingTypePropertyReadOnly = 1 << 16, YYEncodingTypePropertyCopy = 1 << 17, YYEncodingTypePropertyRetain = 1 << 18, YYEncodingTypePropertyNonatomic = 1 << 19, YYEncodingTypePropertyWeak = 1 << 20, YYEncodingTypePropertyCustomGetter = 1 << 21, YYEncodingTypePropertyCustoSetter = 1 << 22, YYEncodingTypePropertyDynamic = 1 << 23, };
通过这个位移枚举的名字,我了解到了 TypeEncoding 这个OC的runtime的知识点。关于Type Encodings 的官方解释 , @encode 是一个编译器指令,返回个内部表示的字符串 , 比如: @encode(int)
→ i ,作用就是可以加快运行时库的消息分发
, @encode 是一个编译器指令,返回个内部表示的字符串 , 比如: @encode(int)
→ i ,作用就是可以加快运行时库的消息分发。
图1-1-2
上图1-1-2 就是OC 的类型编码, 值得注意是 1. 一个指针类型编码是前面加^而char * 却有自己的编码(*),意味着它吧字符串类型当做一个整体。
2. 还有一点是没有标出来的,BOOL 类型 就是被当做一个char ,而不是一个 int 类型。
在接着看位移枚举中他们的枚举值 0XFF,低八位表示 值类型,0xFF00高八位,表示限定符类型,0xFF0000十六位到二十四位,表示属性修饰词的类型。见下图1-1-3为低八位
ps:这个东西真是巧妙,居然还可以这么玩,6666
图1-1-3
在向下看, 它写了个函数
// 这里声明一个函数,来处理类型编码(type Encoding)和枚举的转换, YYEncodingType YYEncodingGetType(const char *typeEncoding);
切换到YYClassInfo.m 文件看下它的实现
YYEncodingType YYEncodingGetType(const char *typeEncoding) { // 判断外部传入值 是不是nil,如果为空 ,返回 YYEncodingTypeUnknown // 转换const 限定符 char *type = (char *)typeEncoding; if (!type) return YYEncodingTypeUnknown; size_t len = strlen(type); if (len == 0) return YYEncodingTypeUnknown; //https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1 encoding type 和 qualifier // 找出修饰语 YYEncodingType qualifier = 0; bool prefix = true; // 可能多个修饰符 while (prefix) { 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; case 'R': { qualifier |= YYEncodingTypeQualifierByref; type++; } break; case 'V': { qualifier |= YYEncodingTypeQualifierOneway; type++; } break; default: { prefix = false; } break; } } // 是否还存在后续的字符 len = strlen(type); if (len == 0) return YYEncodingTypeUnknown | qualifier; // 查找数据类型 switch (*type) { 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; } default: return YYEncodingTypeUnknown | qualifier; } }
上面的注释已经和很清楚了 ,然后切换回YYClassInfo.h
声明了一个抽象类 ,YYClassIvarInfo
@interface YYClassIvarInfo : NSObject @property(nonatomic,assign,readonly) Ivar ivar; @property(nonatomic,strong,readonly)NSString *name; @property(nonatomic,assign,readonly)ptrdiff_t offset; //内存地址偏移 @property(nonatomic,strong,readonly)NSString *typeEncoding; @property(nonatomic,assign,readonly) YYEncodingType type; -(instancetype)initWithIvar:(Ivar)ivar; @end
然后到YYClassInfo.m 去实现
- (instancetype)initWithIvar:(Ivar)ivar { // 初始化判空 如果为空 就返回nil if (!ivar) return nil; self = [super init]; _ivar = ivar; // 获取成员变量的名称 const char *name = ivar_getName(ivar); if (name) { // 把c的字符串转化成oc的字符串 _name = [NSString stringWithUTF8String:name]; } _offset = ivar_getOffset(ivar); // 获取类型编码 const char *typeEncoding = ivar_getTypeEncoding(ivar); if (typeEncoding) { // 转为oc的字符穿 _typeEncoding = [NSString stringWithUTF8String:typeEncoding]; // 转成枚举值 _type = YYEncodingGetType(typeEncoding); } return self; }
最后总结一句话,就是通过一个抽象类来返回需要的实例变量的信息。在切换回YYClassInfo.h
下面又声明两个类似的类 YYClassMethodInfo 和 YYClassPropertyInfo
/** 方法 的信息 */ @interface YYClassMethodInfo : NSObject @property (nonatomic,assign,readonly) Method method; @property (nonatomic,strong,readonly) NSString *name; @property (nonatomic,assign,readonly) SEL sel; // 一种数据类型, 调用方法,可以通过方法名还有SEL 调用 @property (nonatomic,assign,readonly) IMP imp; //方法实现的地址 @property (nonatomic,strong,readonly) NSString *typeEnconding; @property (nonatomic,strong,readonly) NSString *returnTypeEncoding; @property (nullable,nonatomic,strong,readonly) NSArray<NSString *> *argumentTypeEncodings; -(instancetype)initWithMethod:(Method)method; @end /** 属性 的信息 */ @interface YYClassPropertyInfo : NSObject @property (nonatomic,assign,readonly) objc_property_t property; // 类的属性列表 @property (nonatomic, strong, readonly) NSString *name; @property (nonatomic, assign, readonly) YYEncodingType type; @property (nonatomic, strong, readonly) NSString *typeEncoding; @property (nonatomic, strong, readonly) NSString *ivarName; @property (nullable, nonatomic, assign, readonly) Class cls; @property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocls; @property (nonatomic, assign ,readonly) SEL getter; @property (nonatomic,assign,readonly) SEL setter; -(instancetype)initWithProperty:(objc_property_t)property; @end
先从这个 YYClassMethodInfo 方法类说起, 不怎么熟悉的有三个 Method SEL IMP
IMP和SEL 我已经打上注释了,剩下一个Method,去官方文档里找下发现下图1-1-4和图1-1-5
图1-1-4
图1-1-4说明Method是个结构体,
图1-1-5
图1-1-5中可以看出这个结构体类的定义,需要注意的typs是方法的参数类型
1.method_name
:方法名为此方法的方法签名,相同函数名和参数的方法名是一样的
2.method_types
: 描述方法的参数类型
3.method_imp:
方法真实实现代码块的地址指针,可像C 一样直接调用
在看下 YYClassMethodInfo 类的实现
- (instancetype)initWithMethod:(Method)method { if (!method) return nil; self = [super init]; _method = method; // Method获取方法的名称 _sel = method_getName(method); // 方法的实现地址 _imp = method_getImplementation(method); // SEL 获取方法名 const char *name = sel_getName(_sel); if (name) { _name = [NSString stringWithUTF8String:name]; } // 获取类型 const char *typeEncoding = method_getTypeEncoding(method); if (typeEncoding) { _typeEncoding = [NSString stringWithUTF8String:typeEncoding]; } // 获取返回值类型 char *returnType = method_copyReturnType(method); if (returnType) { _returnTypeEncoding = [NSString stringWithUTF8String:returnType]; // 但凡 通过copy retain alloc 系统方法得到的内存,必须使用relea() 或 free() 进行释放 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; }
在 看下面的 YYClassPropertyInfo 类的实现
//https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW5 属性的encongding type
- (instancetype)initWithProperty:(objc_property_t)property { if (!property) return nil; self = [self init]; _property = property; // 1. 获取属性名称 const char *name = property_getName(property); if (name) { _name = [NSString stringWithUTF8String:name]; } // 2.获取每一个属性的编码字符串(所有特性)看下图1-1-6 YYEncodingType type = 0; unsigned int attrCount;//存储特性数量 objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount); // 3. 编译每一个属性的 objc_property_attribute_t attrs是一个 存储结构体(name和value)的数组 for (unsigned int i = 0; i < attrCount; i++) {
// 3.1 根据objc_property_attribute_t 中的name 做一些事 switch (attrs[i].name[0]) { // T 代码属性的类型编码 case 'T': { // Type encoding if (attrs[i].value) { _typeEncoding = [NSString stringWithUTF8String:attrs[i].value]; type = YYEncodingGetType(attrs[i].value); // 计算属性的实体类型
if((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length){
NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding]; // 字符串扫描 详情在下面
if (![scanner scanString:@"@"" intoString:NULL]) continue;
NSString *clsName = nil;
if ([scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@""<"] intoString:&clsName]) {
if (clsName.length) _cls = objc_getClass(clsName.UTF8String); //这个 除了一个错误,让我注意到下面这个
/**
object_getClass(<#id _Nullable obj#>) 参数是id 指针,如果是一个实例对象就返回该实例的类,如果是一个类,返回的是元类名(isa指向的类)
objc_getClass(<#const char * _Nonnull name#>) 参数是字符串,就是根据字符串获取类名
*/
}
NSMutableArray *protocols = nil;
while ([scanner scanUpToString:@"<" intoString:NULL]) {
NSString *protocol = nil;
if ([scanner scanUpToString:@">" intoString:&protocol]) {
if (protocol.length) {
if(!protocol) protocols = [NSMutableArray new];
[protocols addObject:protocol];
}
}
[scanner scanString:@">" intoString:NULL];
}
_protocls = protocols;
}
} break;
case 'V': { // Instance variable if (attrs[i].value) { _ivarName = [NSString stringWithUTF8String:attrs[i].value]; } } break; case 'R': { type |= YYEncodingTypePropertyReadonly; } break; case 'C': { type |= YYEncodingTypePropertyCopy; } break; case '&': { type |= YYEncodingTypePropertyRetain; } break; case 'N': { type |= YYEncodingTypePropertyNonatomic; } break; case 'D': { type |= YYEncodingTypePropertyDynamic; } break; case 'W': { type |= YYEncodingTypePropertyWeak; } break; case 'G': { // getter 方法 type |= YYEncodingTypePropertyCustomGetter; if (attrs[i].value) { _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]); } } break; case 'S': { // setter 方法 type |= YYEncodingTypePropertyCustomSetter; if (attrs[i].value) { _setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]); } } // break; 作者这里注视了break,说下面代码是解释,我是没看出来,我个人的理解是如果有setter方法,就一定有getter方法(如果只存不取,没意义),所以setter 不跳出循环,直到getter才跳 default: break; } } if (attrs) { free(attrs); attrs = NULL; } _type = type; // 获取setter 和 getter 方法 if (_name.length) { if (!_getter) { _getter = NSSelectorFromString(_name); } if (!_setter) { _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]); } } return self; }
关于 NSScanner 的具体使用,参考链接:http://blog.csdn.net/hdfqq188816190/article/details/53170711
图1-1-6
emmmm.... 属性 方法 实例变量 的抽象类都已经搞定了,之后就直接调用编码就OK了 。切换回 YYClassInfo.h 文件,接着往下看声明了一个关于类信息的类YYClassInfo,
@interface YYClassInfo : NSObject @property(nonatomic,assign,readonly) Class cls; @property(nullable,nonatomic,assign,readonly) Class superCls; @property(nullable,nonatomic,assign,readonly) Class metaCls; @property(nonatomic,readonly) BOOL isMeta; @property(nonatomic,strong,readonly) NSString *name; @property(nullable,nonatomic,strong,readonly) YYClassInfo *superClassInfo; @property(nullable,nonatomic,strong,readonly)NSDictionary<NSString *,YYClassIvarInfo *> *ivarInfos; @property(nullable,nonatomic,strong,readonly)NSDictionary<NSString *,YYClassMethodInfo *> *methodInfos; @property(nullable,nonatomic,strong,readonly)NSDictionary<NSString *,YYClassPropertyInfo *> *propertyInfos; /** 如果这个类改变了,你就要调用这个方法去更新这个类的信息;(原作者举的例子:使用 class_addMethod(),添加了一个方法,你就该调用本方法,去更新) 之后调用needUpdate ,它会返回YES,然后调用 classInfoWithClass 或者 classInfoWithClassName 来得到已经更新过得类信息; */ -(void)setNeedUpdate; /** 如果返回了YES,你应该停止使用这个实例,要要用 classInfoWithClass 或者 classInfoWithClassName 来得到已经更新过得类信息; */ -(BOOL)neeUpdate; /** 通过类得到一个类的信息(在的第一次接触的时候会缓存该类及父类的信息), 这个方法是线程安全的, return 一个类信息,或者nil(发生错误) */ +(nullable instancetype)classInfoWithClass:(Class)cls; /** 通过类名得到一个类的信息在的第一次接触的时候会缓存该类及父类的信息), 这个方法是线程安全的, return 一个类信息,或者nil(发生错误) */ +(nullable instancetype)classInfoWithClassName:(NSString *)className; @end
我觉的我的注释写的听清楚的,就不多说了,去他的实现文件 看看,
@implementation YYClassInfo{ BOOL _needUpdate; } -(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:cls]; return self; } -(void)_update{ _ivarInfos = nil; _methodInfos = nil; _propertyInfos = nil; Class cls = self.cls; unsigned int methodCount = 0; Method *methods = class_copyMethodList(cls, &methodCount); if (methods) { NSMutableDictionary *methodInfos = [NSMutableDictionary new]; _methodInfos = methodInfos; for (unsigned int i = 0; i<methodCount; i++) { YYClassMethodInfo *info = [[YYClassMethodInfo alloc]initWithMethod:methods[i]]; if (info.name) { methodInfos[info.name] = info; } free(methods); } } unsigned int propertyCount = 0; objc_property_t *properties = class_copyPropertyList(cls, &propertyCount); if (properties) { NSMutableDictionary *propertyInfos = [NSMutableDictionary new]; _propertyInfos = propertyInfos; for (unsigned int i = 0; i<propertyCount; i++) { YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc]initWithProperty:properties[i]]; if (info.name) { propertyInfos[info.name] = info; } } free(properties); } unsigned int ivarCount = 0; Ivar *ivars = class_copyIvarList(cls, &ivarCount); if (ivars) { NSMutableDictionary *ivarInfos = [NSMutableDictionary new]; _ivarInfos = ivarInfos; for (unsigned int i =0; i < ivarCount; i++) { YYClassIvarInfo *info = [[YYClassIvarInfo alloc]initWithIvar:ivars[i]]; if (info.name) { ivarInfos[info.name] = info; } } free(ivars); } if(!_ivarInfos) _ivarInfos = @{}; if(!_methodInfos) _methodInfos = @{}; if(!_propertyInfos) _propertyInfos = @{}; _needUpdate = NO; } -(void)setNeedUpdate{ _needUpdate = YES; } -(BOOL)neeUpdate{ return _needUpdate; } +(instancetype)classInfoWithClass:(Class)cls{ if(!cls) return nil; static CFMutableDictionaryRef classCache;//这里使用Core Foundation 框架的字典,性能速度是有提升,但就是写起来麻烦些,具体的详情参考下面
static CFMutableDictionaryRef metaCache;
static dispatch_semaphore_t lock; // 信号量
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); lock = dispatch_semaphore_create(1); }); dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache,(__bridge const void *)cls); if (info && info -> _needUpdate) { [info _update]; } dispatch_semaphore_signal(lock); if (!info) { info = [[YYClassInfo alloc]initWithClass:cls]; if (info) { dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); CFDictionarySetValue(info.isMeta ? metaCache :classCache, (__bridge const void *)cls, (__bridge const void *)info); dispatch_semaphore_signal(lock); } } return info; } +(instancetype)classInfoWithClassName:(NSString *)className{ Class cls = NSClassFromString(className); return [self classInfoWithClass:cls]; } @end
关于CFDictonary的详情 参考:http://www.jianshu.com/p/a39509bb24eb
关于 GCD 信号量知识,参考:http://www.cnblogs.com/yajunLi/p/6274282.html
ps:怪不得YYModel 只有四个文件 , 他把类都都写在一个文件里了 - -|| 吐血。。。