iOS 消息机制与三次拯救

消息机制 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:消息获得函数的参数和返回值类型,会有两种情况:

  1. 有返回值:如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送- forwardInvocation:消息给目标对象
  2. 无返回值: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

参考文献

iOS 消息转发机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值