iOS 消息转发机制

文章目录
一、消息发送概述
1、消息发送机制
2、消息转发机制
二、消息是如何转发
1、Method  resolution
2、Fast fowarding
3、Normal forwarding

一、消息概述

1、消息发送机制
在OC中,方法的调用不再理解为对象调用其方法,而是要理解成对象接收消息,消息的发送采用"动态绑定"机制,具体会调用哪个方法直到运行时才能确定,确定后才会去执行绑定的代码。
方法的调用实际上就是告诉对象要干什么,给对象传递一个消息,对象为接收者(receiver),调用的方法及参数就是消息(message),给一个对象传递消息表达为[receiver message];接收者的类型可以通过动态类型识别在运行时确定。
在消息传递机制中,当开发者编写[receiver message];语句发送消息后,编译器都会将其转化成对应的一条objc_msgSend C语言消息发送原语。具体格式为: void objc_msgSend(id self,SEL cmd,....)
这个原语函数参数可变,第一个参数填入消息的接收者,第二个参数就是消息"选择子",后面跟着可选的消息的参数。有了这些参数,objc_msgSend就可以通过接收者的isa指针,到其类对象的方法列表中以选择子的名称为"键"寻找对应的方法。若找到对应的方法,则转到其实现代码执行,否则继续从父类中寻找,如果到根类还是无法找到对应的方法,说明该接收者对象响应该消息,那么就会触发消息转发机制,给开发者最后一次挽救程序crash的机会。

2、消息转化机制
如果在消息传递过程中,接收者无法响应收到的信息,那么就会触发进入消息转发机制。
消息转发依次提供了3道防线,任何一个起作用都可以挽救此次消息转发。
按照先后顺序3道防线依次为

(1)动态补加方法实现

 +(BOOL)resolveInstanceMethod:(SEL)sel
 +(BOOL) resolveClassMethod:(SEL)sel

(2)直接返回消息转发到的对象(将消息发送给另一对象去处理)

- (id)forwardingTargetForSelector:(SEL)aSelector
+ (id)forwardingTargetForSelector:(SEL)aSelector

(3)手动生成方法签名并转发给另一对象

- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
+ (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
+ (void)forwardInvocation:(NSInvocation *)anInvocation

二、消息是如何转发

unrecognized selector 错误主要发生在消息接收者无法正确响应发来的消息时,即无法找到消息对应的实现时。
unrecognized selector 错误指无法识别selector.即receiver无法处理发出的的消息。根据消息传递机制和动态绑定机制可知,想接收者对象发送消息后,会根据其isa指针到其类对象的方法列表中以方法名为键找对应方法的实现,如果找不到,那么启动消息转发机制,但如果消息转发机制仍然无法弥补,那么久意味着接收者无法响应及正确处理该消息,就会报unrecognized selector 错误导致程序crash。

消息在运行时系统的翻译模式如下 :


引入 : #import <objc/message.h>

[object sendMessage]  

// 在编译时,会被解释成     
objc_msgSend(object, @selector(sendMessage));

备注 : 如果上述 代码 在你工程编译不通过的话,可在 Build Settings 将 Enable Strict Checking of objc_msgSend Calls 将其值设置成 NO 即可。

可理解成 向 对象 object 发送 sendMessage 消息。当向object 发送消息时,runtime库会根据对象的 isa 指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。

object : 当前消息的接受者
sendMessage : 发送消息内容

但是,在这之前,runtime 会给出 三次 的拯救 机会。查看了一些资料,这三次机会可大概描述成 如下:

1、Method resolution

2、Fast fowarding

3、Normal forwarding

下面是一个具体的例子

场景 : 定义Person类继承NSObject

// Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject

@end


//Person.m 

#import "Person.h"

@implementation Person

@end


// main.m文件
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Person.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        Person *jack = [Person new];
        [jack performSelector:@selector(speakIMP:) withObject:@"jack"];
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

运行报错如下:

-[Person speakIMP]: unrecognized selector sent to instance 0x60400001b260

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person speakIMP]: unrecognized selector sent to instance 0x60400001b260'

说明 : Person 及其父类皆未找到 speakIMP 方法未实现

1、Method resolution

objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发。

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(speakIMP:)) 
    {
      
      // 方式一: 调用下面C 函数
     //class_addMethod([self class], sel, (IMP)speakIMP, "b@:@");

     //方式二 :调用下面OC 函数  class_getMethodImplementation 改变现有的实现
     class_addMethod([self class], sel, class_getMethodImplementation(self,@selector(dynamicAddMethod:)), "b@:@");
      return YES;
    }
    
    return [super resolveInstanceMethod:sel];
}// 实例方法


+ (BOOL) resolveClassMethod:(SEL)sel
{
    return [super resolveClassMethod:sel];
    
}// 类方法



// 实现C 函数
BOOL speakIMP(id self,SEL _cmd,NSString*name){

    NSLog(@"方法添加成功,且接受参数为:%@",name);
   
    return YES;
}


// oc 函数
- (BOOL)dynamicAddMethod:(NSString *name)
{
    if (name != nil) 
    { 
        NSLog(@"方法添加成功,且接受参数为:%@",name);
        return YES;
    }
    
    return NO;
}

关于 class_addMethod 参数的说明

1、第一个参数 : Class _Nullable cls。 在哪个类添加方法

2、第二个参数 : SEL _Nonnull name。添加的方法的编号

3、第三个参数 : IMP _Nonnull imp 。方法的实现(其实就是函数指针)。这个C函数,至少包含两个参数 id self 和 SEL _cmd

4、第四个参数 : const char * _Nullable types 。v代表 void , : 代表 SEL

例如 :
”v@:”意思就是这已是一个void类型的方法,没有参数传入。

“i@:”就是说这是一个int类型的方法,没有参数传入。

”i@😡”就是说这是一个int类型的方法,又一个参数传入。

注意 : performSelector是运行时系统负责去找方法的。

假设Person 没有实现 resolveClassMethod 方法,或者 返回NO,那么就会进行消息转发(Fast fowarding)。

.
.
.

2、Fast fowarding(让别的对象去执行这个函数)

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 **只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。**否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    OtherObject *jack = [[OtherObject alloc] init];
    
    if ([jack respondsToSelector:aSelector]) 
    {

         // 相当于[jack speakIMP:@"jack"];
           return jack;
        
    }

    return [super forwardingTargetForSelector:aSelector];
    
} // 只要不返回 nil 和 self  消息发送过程就会被重启

.
.
.

3、Normal forwarding

这一步是Runtime最后一次给你挽救的机会。
首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。

(1)如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。

(2)如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
    //查找父类签名
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
    
    if(methodSignature == nil)
    {
      
        methodSignature = [NSMethodSignature signatureWithObjCTypes:"@:@"];
    }
    
    return methodSignature;
    
    
    
}//如果签名 不为nil ,那么runtime 会创建一个 NSInvocation 对象,并发送forwardInvocation:消息 


- (void)forwardInvocation:(NSInvocation *)anInvocation
{
  
    SEL sel = anInvocation.selector;
    
    OtherObject *jack = [[OtherObject alloc] init];
    
    if ([jack respondsToSelector:sel]) 
    {
      
        [anInvocation invokeWithTarget:jack];
    }

}



- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    NSLog(@"程序crash了");
    
}//当方法签名为nil,调用此方法。程序crash。

runtime 消息发送的三次补救机会,可总结入下图所示
这里写图片描述

demo 传送门

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值