iOS runtime,详细介绍消息转发流程
方法查找原理
在之前的文章中,写过在进行方法调用的时候,runtime的消息转发流程
- 先去缓存中查找
- 如果缓存没有找到,通过 isa 指针找到当前的类的对象,然后去方法列表中查找
- 如果当前方法列表中还是没有,就通过 superClass 指针在自己的父类中已上面两个流程去查找,一直找到根NSObject类
为什么要有第一步?因为我们的方法太多了,每次都遍历一次 objc_method_list
列表去查找,这样就效率太低了,所以就把经常用的方法给缓存起来了, objc_method_list 是 objc_class 结构体的一员,下面是 runtime 源码 objc4-756.2 版本的 objc_class 结构体
源码结构体分析
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
其中 objc_cache 就是用来干这个事情的,看到这个源码的第一印象,我想到的就是 YYModel 的源码,他的源码中的几个类封装的就是类似于源码的这个结构体,再来看几个
struct objc_protocol_list {
struct objc_protocol_list * _Nullable next;
long count;
__unsafe_unretained Protocol * _Nullable list[1];
};
struct objc_ivar {
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
}
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
看到这些,我想很多人都眼前一亮,或多或少看到过或者用到过.
从 objc_class 可以看到,它里面保存了,父类指针,类名,版本号,信息,实力大小,变量列表,方法列表,方法缓存列表,协议列表.
我们再来看下源码中的 objc_object
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
可以看到有个 isa 指针,类对象的 isa 指针指向元类,这里有一篇博客的图画的很好可以参考他的总结
* objc_object 的isa指针指向他的类对象
* 类对象的isa指针指向元类,superClass指针指向他的父类的类对象,也就是nsobject
* 元类的isa指针指向nsobject元类。
* 上面说了isa指针指向他的元类,所以nsobject的isa指针就指向nsobject元类
* 所以这样就形成了一个闭环
测试
- (void)testClass{
TestClass *test = [[TestClass alloc] init];
Class c1 = [test class];
Class c2 = [TestClass class];
NSLog(@"%d",c1 == c2);
NSLog(@"%@",[c1 class]);
// 获取isa指针指向的对象,这里其实获取的是实例对象c1的类对象,也就是TestClass
NSLog(@"%@",test);
NSLog(@"%@",[test class]);
NSLog(@"%@",[TestClass class]);
NSLog(@"%@",object_getClass(test));
NSLog(@"%@",object_getClass([test class]));
NSLog(@"%@",object_getClass([TestClass class]));
NSLog(@"%d",class_isMetaClass([test class]));
NSLog(@"%d",class_isMetaClass([TestClass class]));
NSLog(@"%d",class_isMetaClass(object_getClass(test)));
NSLog(@"%d",class_isMetaClass(c);
NSLog(@"%d",class_isMetaClass(object_getClass([TestClass class])));
}
打印结果
2019-12-18 20:05:28.646099+0800 blogTest[43808:3772452] 1
2019-12-18 20:05:28.646263+0800 blogTest[43808:3772452] TestClass
2019-12-18 20:05:28.646422+0800 blogTest[43808:3772452] <TestClass: 0x6000015e0a90>
2019-12-18 20:05:28.646565+0800 blogTest[43808:3772452] TestClass
2019-12-18 20:05:28.646680+0800 blogTest[43808:3772452] TestClass
2019-12-18 20:05:28.646777+0800 blogTest[43808:3772452] TestClass
2019-12-18 20:05:28.646890+0800 blogTest[43808:3772452] TestClass
2019-12-18 20:05:28.646989+0800 blogTest[43808:3772452] TestClass
2019-12-18 20:05:28.647170+0800 blogTest[43808:3772452] 0
2019-12-18 20:05:28.647393+0800 blogTest[43808:3772452] 0
2019-12-18 20:05:28.647725+0800 blogTest[43808:3772452] 0
2019-12-18 20:05:28.648001+0800 blogTest[43808:3772452] 1
2019-12-18 20:05:28.648252+0800 blogTest[43808:3772452] 1
可以看出 c1 == c2,因为c1 通过 class 获取到的是他的类对象,类本身获取class也是其本身,所以c1=c2,object_getClass 是通过他的 isa 指针获取他的类对象,所以 [test class] 获取的是他的类对象,object_getClass([test class]),是通过他的类对象的isa指针,指向他的元类对象, class_isMetaClass 是获取是否为元类,所以分析上面的代码,class_isMetaClass([test class]) 为0,因为通过class获取的是当前的类对象,而类对象不是元类对象,所以为0,而 class_isMetaClass(object_getClass([test class])) ,是先获取他的类对象,然后通过类对象的isa指针,指向他的元类对象,class_isMetaClass(object_getClass([test class])),然后再打印是否为元类对象,所以为1.
总结:
实例对象objc_object其实是一个结构体,他只有一个成员变量isa,指向他的类对象,这个类对象存放了变量和实例方法等数据,而类对象是通过元类创建的,元类中保存了类方法和类变量
消息转发
当我们调用衣蛾方法的时候,如[self test],其实就是为这个方法添加了一个编号SEL(test),sel 是一个实例,而IMP是最终指向实现程序的内存地址的指针,当调用这个方法的时候,系统去方法列表中查找,ios 之所以不可以使用重载,就是方法名字相同而参数不同,是因为sel只记住了方法名字,而没有记住参数,所以是不行的
如果我们调用了一个方法之后,就会在当前的类对象的方法列表中去查找,如果查不到,就会沿着继承树一直向上查找,知道根部nsobject,如果还是找不到就会调用doesNotRecognizeSelector:方法报unrecognized selector。
消息转发流程
1.+(BOOL)resolveInstanceMethod:(SEL)sel
2.-(id)forwardingTargetForSelector:(SEL)aSelector
3. -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
-(void)forwardInvocation:(NSInvocation *)anInvocation
1.动态方法解析(resolveInstanceMethod)
oc 在运行时,会调用 +(BOOL)resolveInstanceMethod:(SEL)sel
或者 +(BOOL)resolveClassMethod:(SEL)sel
两个方法,让你有机会提供一个函数实现,如果提供了函数实现返回了 yes , 那么系统会重新进行一次消息发送,如下
void toPrint(id obj,SEL _cmd){
printf("to test resolveInstanceMethod");
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(print)) {
class_addMethod([self class], sel, (IMP)toPrint, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
可以看到我们运行时,添加了一个方法,并且提供了实现返回了yes,当我们外面调用的时候,也成功打印了。
2.交给其他接受者处理(forwardingTargetForSelector)
如果实现了这个方法,那么runtime 就允许你,转发给其他对象的机会
@interface Forwarding : NSObject
@end
@implementation Forwarding
- (void)print{
NSLog(@"forwarding to print");
}
@end
-(id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(print)) {
return [Forwarding new];
}
return [super forwardingTargetForSelector:aSelector];
}
这里我们告诉runtime,让 forwarding 类来处理这个方法。
3.消息转发最后一步,完整的消息转发 (methodSignatureForSelector)
如果上面两个两个方法都没有相应,没关系,runtime还提供了最后一个转发,完整消息转发
首先会调用 methodSignatureForSelector
来获取完整的 签名,也就是返回值类型,参数类型,如果返回nil,那么runtime会发出doesNotRecognizeSelector
的异常,如果返回了一个签名,那么runtime 就会创建一个NSInvocation对象,并且 发送forwardInvocation,给目标对象.
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(print)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return nil;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
SEL sel = anInvocation.selector;
Forwarding *f = [[Forwarding alloc] init];
if ([f respondsToSelector:sel]) {
[anInvocation invokeWithTarget:f];
} else {
[self doesNotRecognizeSelector:sel];
}
}
可以看到,我们首先返回给了runtime 一个方法签名,v@:,表示返回值是void,将self和sel作为参数传递,关于方法签名相关,之后的文章我会讲解,其实官方文档有相关的介绍,当我们返回了一个签名之后,就调用forwardInvocation,进行转发。