文章目录
消息机制 objc_msgSend();
函数引出
我们写一句OC代码
Person *person = [Person alloc];
转译为cpp文件就是
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc"));
为了方便理解,我们去掉转化的代码
Person *person = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
可以看到每执行一句OC代码,就调用了一次objc_msgSend()函数
执行过程
在OC中,方法的调用不再理解为对象调用其方法,而是要理解成对象接收消息,消息的发送采用"动态绑定"机制,具体会调用哪个方法直到运行时才能确定,确定后才会去执行绑定的代码
方法的调用实际上就是告诉对象要干什么,给对象传递一个消息,对象为接收者(receiver),调用的方法及参数就是消息(message),给一个对象传递消息表达为[receiver message];接收者的类型可以通过动态类型识别在运行时确定
在消息传递机制中,当开发者编写[receiver message];语句发送消息后,编译器都会将其转化成对应的一条objc_msgSend C语言消息发送原语。具体格式为: void objc_msgSend(id self,SEL cmd,…)
objc_msgSend的执行流程可以分为三大阶段:
- 消息发送
- 动态方法解析
- 消息转发
第一阶段 – 消息发送
objc_msgSend的第一个参数为接收者,第二个参数是消息"选择子",后面跟着可选的消息的参数
所以当我们接收到消息时,首先会在接收者的类中查找对应的方法,如果没找到就在其父类中查找。如果一直到没有父类还没有找到该方法,那么我们就要进入第二个阶段–动态方法解析
第二阶段 – 动态方法解析
如果当时没有方法,可以在程序运行时添加方法
例如:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person doSomething];
}
return 0;
}
在Person文件里我什么方法都没有写,包括doSomething方法。报错:
然后在Person.m文件里写:
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
- (void)other {
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// 我让其执行了other方法
Method method = class_getInstanceMethod(self, @selector(other));
// 如果是需要执行doSomething方法的
if (sel == @selector(doSomething)) {
// 就执行other方法
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
// 返回已经执行了动态方法解析
return YES;
}
return [super resolveInstanceMethod:sel];
}
打印情况:
传进来的self执行了other方法,拯救了此次操作
注意:因为我使用的是实例变量方法,所以调用了 - (BOOL)resolveInstanceMethod:(SEL)sel 方法,如果是类方法就要使用 + (BOOL)resolveClassMethod:(SEL)sel 方法进行拯救
如果在这一步没有进行动态方法解析,就只能进入第三阶段–消息转发
第三阶段 – 消息转发
Fast fowarding
上面的阶段都是由接收者去处理的,而到了第三阶段,就只能让别的对象去执行这个函数了
如果目标对象实现了- forwardingTargetForSelector:方法,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。
例如:
// Person.m文件里
#import "Person.h"
#import "TAY.h"
#import <objc/runtime.h>
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(doSomething)) {
return [[TAY alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
// TAY.m文件里
#import "TAY.h"
@implementation TAY
- (void)doSomething {
NSLog(@"%s", __func__);
}
@end
Normal forwarding
这是Runtime给我们最后一次的拯救机会,这一步会创建一个NSInvocation对象,所以比上面的慢点,故上面转发方法叫Fast fowarding
首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型,会有两种情况:
- 有返回值:如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送- forwardInvocation:消息给目标对象
- 无返回值:Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了
关于函数签名的官方文档
我们来看看有返回值怎么做:
//如果签名 不为nil ,那么runtime 会创建一个 NSInvocation 对象,并发送forwardInvocation:消息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(doSomething)) {
//如果这里是nil,就报错
return [NSMethodSignature signatureWithObjCTypes:"i20@0:8i16"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:[[TAY alloc] init]];
}
注意:当进行到 - forwardInvocation: 时就不一定非要返回一个对象,随便返回什么都可以,比如:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"123");
}
或者什么都不做,只要执行了这个方法也不会报错
- (void)forwardInvocation:(NSInvocation *)anInvocation {
}
三次拯救
如果在消息传递过程中,接收者无法响应收到的消息,那么就会触发进入消息转发机制
消息转发依次提供了三道防线,任何一次都可以拯救这次消息转发
三道防线依次为:
- 动态补加方法实现 ----- 即上文 第二阶段 – 动态方法解析
- 直接返回消息转发到的对象(将消息发送给另一对象去处理) ---- 即上文 第三阶段 – 消息转发 Fast forwarding
- 手动生成方法签名并转发给另一对象 ---- 即上文 第三阶段 – 消息转发 Normal forwarding