iOS中的Runtime(消息转发)

856 阅读6分钟

这是我参与8月更文挑战的第31天,活动详情查看:8月更文挑战

消息转发

在编译期,向类发送了其无法解读的消息并不会报错,因为在运行期间可以继续向类中动态添加方法,所以编译器在编译时无法确定类中到底会不会有某个方法实现。当对象接收到无法解读的消息后,就会启动“消息转发”机制。

消息转发分为俩大阶段

  • 动态方法解析
  • 完整的消息转发机制

动态方法解析

对象在收到无法解读的消息后,首先将调用其所属类的类方法:

  • 判断这个类是否可以新增一个实例方法处理这个无法解读的消息
    + (BOOL)resolveInstanceMethod:(SEL)selector
    
  • 判断这个类是否可以新增一个类方法处理这个无法解读的消息
    + (BOOL)resolveClassMethod:(SEL)selector
    

以上方法参数就是那个无法解读的消息,返回值是个 BOOL 类型,表示这个类是否可以新增一个实例方法或类方法解决无法解读的消息。这些方法设计的目的是为了给类利用 class_addMethod 添加方法的机会,在返回值中,无论返回 YES 还是 NO,系统都会尝试用 SEL 来寻找方法实现,如果找到函数实现,则执行,所以无论返回 YESNO 都会进入完整的消息转发机制。

完整的消息转发机制

如果运行系统已经把第一阶段执行完成,那么指定对象就无法再以动态新增方法的手段来处理无法解读的消息了。此时,运行期系统会请求对象以其他手段处理消息。这里又分为两个阶段。

当对象无法解读消息时,可以看看有没有其他对象可以处理。若有,则运行期系统会把消息转发给可以处理消息的那个对象,消息转发过程结束;若没有备援对象接收,则启动完整的消息转发机制

备援对象

在第二阶段中,当前对象还有第二次机会处理无法解读消息。这个阶段,运行系统会看看有没有其他对象可以处理该消息,系统提供方法如下

- (id)forwardingTargetForSelector:(SEL)selector

以上方法参数就是那个无法解读的消息,若当前对象能找到处理该消息的对象,则将其返回,若找不到,就返回 nil。此时就会进入完整的消息转发机制。

完整的消息转发机制

如果消息处理到这个阶段,唯一可以做的就是启动完整的消息转发机制。系统提供方法如下

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

首先需要创建 NSInvocation 对象,把无法解读消息全部信息封装其中,该NSInvocation 对象包括消息、目标、参数。在这个方法中,可以把 invocation 转发给多个对象,与备援对象不同,备援对象只能转给一个对象。

抛出异常

如果上述阶段都无法解读该消息,系统则会调用doesNotRecognizeSelector:抛出异常,程序 crash。

注:
关于 warning。编译器很好心的报的那个 warning 咋办呢,不管那个小黄条不是一个爱整洁的程序员的风格,所以我们要想办法把它去掉。比较暴力,通过在配置文件中把 Complier Flag-w,对该类去除所有 warning

warning


消息转发全流程

1.若对象无法响应某个消息,则进入消息转发流程
2.通过运行期间的动态方法解析,可以在需要用到某个方法时,再将其加入类中
3.对象可以把其无法解读的消息转交给其他对象处理
4.经过上述两步,如果还是不能处理该消息,那就启动完整的消息转发机制

消息转发全流程.png


消息转发示例

动态方法解析示例(动态添加方法)

Phone 类中并没有- (void)call;方法的实现

@interface Phone : NSObject
- (void)call;
@end

#import "Phone.h"
@implementation Phone

@end

实例调用[phone call]

Phone *phone = [[Phone alloc]init];
[phone call];

程序 carsh 报告

carsh

使用动态方法解析处理无法解读的消息

#import "Phone.h"
#import <objc/runtime.h>

@implementation Phone

//有俩个隐含参数id、SEL
void call(id self, SEL sel){
    NSLog(@"打电话");
}

//第一阶段:动态添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if ([NSStringFromSelector(sel) hasSuffix:@"call"]) {
    
        // 参数1:给哪个类添加方法 
        // 参数2:方法名 
        // 参数3:添加方法的函数实现(函数地址)
        // 参数4:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
        class_addMethod([self class], sel, (IMP)call, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end

log:

打电话

备援对象示例

Phone 类中并没有- (void)call;方法的实现

@interface Phone : NSObject
- (void)call;
@end


#import "Phone.h"
#import "PhoneA.h"
#import <objc/runtime.h>

@implementation Phone
//第一阶段:动态方法解析返回 NO
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    return [super resolveInstanceMethod:sel];
}

//第二阶段:若当前对象能找到处理该消息的对象,则将其返回
- (id)forwardingTargetForSelector:(SEL)aSelector{
    PhoneA *phone = [[PhoneA alloc]init];
    if ([phone respondsToSelector:aSelector]) {
        return phone;
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

备援对象

@interface PhoneA : NSObject

@end

#import "PhoneA.h"
@implementation PhoneA
- (void)call{
    NSLog(@"打电话");
}
@end

实例调用[phone call]

Phone *phone = [[Phone alloc]init];
[phone call];

log:

打电话

完整的消息转发机制示例

Phone 类中并没有- (void)call;方法的实现

@interface Phone : NSObject
- (void)call;
@end


#import "Phone.h"
#import "PhoneA.h"
#import <objc/runtime.h>

@implementation Phone
//第一阶段:动态方法解析返回 NO
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    return [super resolveInstanceMethod:sel];
}

//第二阶段:(1)若当前对象能找到处理该消息的对象,则将其返回,若找不到,就返回 nil
- (id)forwardingTargetForSelector:(SEL)aSelector{
    id a =  [super forwardingTargetForSelector:aSelector];
    return a;
}

//第二阶段:(2)首先寻找方法签名,如果没有,则会调用系统根类的doesNotRecognizeSelector:方法,不会进入到下面的消息分发.
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{

    if(aSelector == @selector(call)){
        PhoneA *phone = [[PhoneA alloc]init];
        return [phone methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}


//如果上面的方法签名找到了,则会调用这个方法.将消息传递给其他对象,可以传递给多个对象,通过anInvocation拿到相应信息
- (void)forwardInvocation:(NSInvocation *)anInvocation{

    if (anInvocation.selector == @selector(call)){
        PhoneA *phone = [[PhoneA alloc]init];
        [anInvocation invokeWithTarget:phone];
    }
}

//寻找方法签名,如果没有找到,则回调这个方法
- (void)doesNotRecognizeSelector:(SEL)aSelector{
    NSLog(@"无法解读,APP crash");
}
@end

备援对象

@interface PhoneA : NSObject

@end

#import "PhoneA.h"
@implementation PhoneA
- (void)call{
    NSLog(@"打电话");
}
@end

实例调用[phone call]

Phone *phone = [[Phone alloc]init];
[phone call];

log:

打电话