前言:

之前一直有了解iOS中的消息转发机制,一直没有总结过。这篇文章就当是对之前碎片知识的总结吧。主要涉及到了runtime的消息传递和如果没有找到对象的方法,系统为我们提供的三次拯救机会。废话不多说,直接进行正文。

 

Runtime的消息传递

我们都知道OC是一门动态语言。OC语言并不能直接被计算机所识别。需要先转换成C语言,然后转成汇编语言,最后转成计算机认识的机器语言。

当我们调用一个对象的方法[objc foo]时,编译器会将代码进行转换成消息发送objc_msgSend(objc foo)。具体执行过程是这样的:

  1. 首先,系统通过objc的isa指针找到它对应的class。
  2. 然后,在class的method list的列表中查找方法foo。
  3. 如果没有找到对应的方法,就去它的父类中查找。
  4. 在这个过程中找到对应的方法,就去通过方法的IMP指针(方法地址指针)执行方法。

当然有的时候我们并没有实现方法foo。例如我们调用了一个不可变字符串的修改方法。其实,修改方法只实现在可变字符串类中。那么程序就会崩溃,报错信息unrecognized selector。不能识别到这个方法。其实在这个崩溃之前,系统给我们提供了三次挽救的机会,这就是消息转发过程。

消息转发

当系统并不能找到你要执行的方法时,会提供三次机会拯救你的程序崩溃。

1、动态方法解析

oc在运行时,对象方法调用会执行 +resolveInstanceMethod: 类方法调用会执行 +resolveClassMethod:方法,这样我们可以在这两个方法中,进行一个处理。通过runtime的动态添加方法,并且将消息转发给备用的接收方法。

ios实现消息转发 ios消息转发解决崩溃_ios实现消息转发

可以看到,我并没有实现foo:方法,但是运行程序并没有崩溃,而且执行了我新定义的newFooMethod方法。

然后,如果这一步我们没有进行相应的处理,程序就会进行到第二步,寻找消息的实现对象。

 

2、定义备用接收者

如果第一步,没有进行拯救,那么程序会进行到这一步,系统会调用- (id)forwardingTargetForSelector:(SEL)aSelector方法。给你一个将消息传递给其他对象的机会。因此,如果想在这一步进行拯救。我们需要定义一个备用的类,来作为处理消息的target。只要我们备用的类中实现了这个方法,那么系统将执行这个方法。

#import <objc/runtime.h>

@interface OtherPerson : NSObject

@end

@implementation OtherPerson

//定义foo方法,处理转发进来的foo方法
- (void)foo {
    NSLog(@"Person foo func");
}

@end

@implementation TestViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self performSelector:@selector(foo)];
    // Do any additional setup after loading the view.
}

void newFooMethod(){
    NSLog(@"Now I am new foo func.");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{

//    if(sel == @selector(foo)){
//        class_addMethod([self class], sel, (IMP)newFooMethod, "v@:");
//        return YES;
//    }
    //直接执行系统方法,不走上边的添加方法,那么找不到就会走下边的转发
    return [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector{

    if(aSelector == @selector(foo)){
        return [OtherPerson new];//设置转发的target处理方法
    }
    
    return [super forwardingTargetForSelector:aSelector];
}



/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

然后运行代码,发现打印了Person foo func。也没有崩溃,说明foo方法已经通过转发给OtherPerson类来执行了。

3、完整消息转发

如果前两个方法没有进行拯救成功,那么消息转发进入到最后一步。如果这一步还不能处理消息,那么Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。

在这里我们需要更大的代价来进行拯救,我们需要先获得方法签名。将方法签名传递给系统的

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法。当这个方法接收到方法签名后,
runtime就会创建一个NSInvocation对象,发送- (void)forwardInvocation:(NSInvocation *)anInvocation给目标对象

#import <objc/runtime.h>

@interface OtherPerson : NSObject

@end

@implementation OtherPerson

- (void)foo {
    NSLog(@"Person foo func");
}

@end

@interface TestViewController ()

@end

@implementation TestViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor yellowColor];
    [self performSelector:@selector(foo)];
    // Do any additional setup after loading the view.
}

void newFooMethod(){
    NSLog(@"Now I am new foo func.");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//    if(sel == @selector(foo)){
        class_addMethod([self class], sel, (IMP)newFooMethod, "v@:");
//        return NO;
//    }
    //走系统的方法,找不到方法,走第二步
    return [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector{

//    if(aSelector == @selector(foo)){
//        return [OtherPerson new];
//    }
    //执行系统的方法,因为没有响应的类,所以会进入到第三步,也就是下边的方法
    return [super forwardingTargetForSelector:aSelector];
}

//这是拯救崩溃的最后一步
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    
    if([NSStringFromSelector(aSelector) isEqualToString:@"foo"]){

        //方法选择器
        SEL fooSEL = @selector(foo);
        //方法签名
        NSMethodSignature *fooSignature = [NSString methodSignatureForSelector:fooSEL];
        //返回一个方法签名
        return fooSignature;
    }
    
    return [super methodSignatureForSelector:aSelector];
}

//通过方法签名进行转发
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    
    SEL sel = anInvocation.selector;
    
    OtherPerson *person = [OtherPerson new];
    if([person respondsToSelector:sel]){
        [anInvocation invokeWithTarget:person];
    }else{
        //没有办法响应
        [self doesNotRecognizeSelector:sel];
    }
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

 

通过运行程序,打印了Person foo func,没有出现程序崩溃。至此,消息转发三次拯救机会就完事了。

下边稍微说一下方法签名

方法签名的获取方式有两种,一种是手动生成方法签名,另一种是自动生成方法签名。

手动生成方法签名

手动生成方法签名,需要传递方法的返回值,参数等信息来通过方法生成签名。例如[NSMethodSignature signatureWithObjCTypes:"@@*"]

这里边参数是一个字符串数组。这个数组包含了方法的返回值、参数等信息。其中第一个@表示方法返回值是一个id类型的对象,而第二个@表示方法接收一个id参数,最后的*表示方法接收一个字符串(char*)类型的参数。其实苹果官方有一个对照表TypeEncoding

自动生成方法签名

一般情况下,我们是通过自动生成方法签名的。也就是我上边代码的形式,不用传递一个c的字符串数组表示方法的返回值参数信息。生成起来也更准确一些,有下边两个方法,都可以生成方法的签名。

// 从类中获取实例方法签名
    fooSignature = [NSString instanceMethodSignatureForSelector:fooSEL];
    // 从类中获取类方法签名
    fooSignature = [NSString methodSignatureForSelector:fooSEL];