iOS消息转发机制分析

为什么要谈消息转发呢,对我们又有什么用处呢?虽然平时开发过程中可能没有太关心,但是我们始终都在接触它,比如说开发过程中我们经常会碰到这样的报错

unrecognized selector sent to instance **

原因是我们调用了一个不存在的方法。其实就是说消息的接受者没有找到对应的selector,在这个时候就启用了消息转发机制,当然,我们可以通过在消息转发的过程中告诉对象如何处理这种情况。
分析之前我们来写个例子,创建一个mac的Command Line Tool程序,创建一个类Person,代码如下:

#import 
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        [p performSelector:NSSelectorFromString(@"eat")];
        [p performSelector:NSSelectorFromString(@"walk")];
    }
    return 0;
}
#import 
@interface Person : NSObject
- (void)eat;
@end
#import "Person.h"

@implementation Person
- (void)eat
{
    NSLog(@"person eat");
}
@end

运行一下,可以想到的结果是

2018-10-26 15:55:47.747779+0800 MsgLine[78325:5469322] person eat
2018-10-26 15:55:47.748122+0800 MsgLine[78325:5469322] -[Person walk]: unrecognized selector sent to instance 0x1007089c0

接下来我们来分析一下,当对象收到无法解读的消息时,都会经过哪几个步骤来处理:

第一步

调用

+(BOOL)resolveInstanceMethod:(SEL)sel
或者
+ (BOOL)resolveClassMethod:(SEL)sel
询问是否添加方法进行处理,我们改下上面的代码

#import "Person.h"
@implementation Person
- (void)eat
{
    NSLog(@"person eat");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"resolveInstanceMethod");
    return [super resolveInstanceMethod:sel];
}
@end

运行结果如下

2018-10-26 15:57:22.804747+0800 MsgLine[78357:5474517] person eat
2018-10-26 15:57:22.805032+0800 MsgLine[78357:5474517] resolveInstanceMethod
2018-10-26 15:57:22.805104+0800 MsgLine[78357:5474517] -[Person walk]: unrecognized selector sent to instance 0x100708610

依然崩溃,但是已经进入了resolveInstanceMethod:方法,我们再次改造

#import "Person.h"
#import 
@implementation Person
- (void)eat
{
    NSLog(@"person eat");
}
- (void)walk
{
    NSLog(@"person walk");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"resolveInstanceMethod");
    if (sel == @selector(walk)){
        return class_addMethod([self class], sel, method_getImplementation(class_getInstanceMethod([self class], sel)), [@"V@:" UTF8String]);
    }
    return [super resolveInstanceMethod:sel];
}
@end

这次运行后就发现没有崩溃了,同时控制台输出正常

2018-10-26 16:12:23.353887+0800 MsgLine[78585:5512283] person eat
2018-10-26 16:12:23.354247+0800 MsgLine[78585:5512283] person walk
当Person收到了未知walk消息的时候,如果是实例方法会首选调用上文的resolveInstanceMethod:方>法,如果我们通过class_addMethod方法动态添加了一个walk的实现方法来解决掉这条未知的消息,时消息转发过程结束。

第二步【如果没有添加方法,返回了NO,那么才有第二步】

询问其他类是否能处理这个消息

- (id)forwardingTargetForSelector:(SEL)aSelector

这次我们进行如下改造

#import 
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        [p performSelector:NSSelectorFromString(@"eat")];
        [p performSelector:NSSelectorFromString(@"walk")];
        [p performSelector:NSSelectorFromString(@"fly")];
    }
    return 0;
}

创建一个新类Plane,同时添加- (void)fly方法

#import 
@interface Plane : NSObject
- (void)fly;
@end
#import "Plane.h"
@implementation Plane
- (void)fly
{
    NSLog(@"Plane fly");
}
@end

main函数里面调用了fly方法,如果我们不什么都不做,最后仍然会抛出unrecognized selector sent to instance **异常,这次我们不动态添加方法了,而是在- (id)forwardingTargetForSelector:(SEL)aSelector里面做文章

#import "Person.h"
#import "Plane.h"
#import 
@implementation Person
- (void)eat
{
    NSLog(@"person eat");
}
- (void)walk
{
    NSLog(@"person walk");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"resolveInstanceMethod");
    if (sel == @selector(walk)){
        return class_addMethod([self class], sel, method_getImplementation(class_getInstanceMethod([self class], sel)), [@"V@:" UTF8String]);
    }
    return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@"forwardingTargetForSelector");
    Plane *plane = [[Plane alloc] init];
    if ([plane respondsToSelector: aSelector]) {
        return plane;
    }
    return [super forwardingTargetForSelector: aSelector];
}
@end

运行之后控制台输出如下

2018-10-26 16:51:30.386475+0800 MsgLine[79164:5590896] person eat
2018-10-26 16:51:30.386752+0800 MsgLine[79164:5590896] person walk
2018-10-26 16:51:30.386776+0800 MsgLine[79164:5590896] resolveInstanceMethod
2018-10-26 16:51:30.386811+0800 MsgLine[79164:5590896] forwardingTargetForSelector
2018-10-26 16:51:30.386843+0800 MsgLine[79164:5590896] Plane fly
这里Plane能够处理这条消息,所以这条消息被plane成功处理,消息转发流程结束,如果不能处理的话,那么才会进入下一步

第三步【如果没有交给其他类进行处理,就进入下一步】

获取方法签名

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

调用

- (void)forwardInvocation:(NSInvocation *)anInvocation

同样,我们修改我们的代码,修改main.m

#import 
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        [p performSelector:NSSelectorFromString(@"eat")];
        [p performSelector:NSSelectorFromString(@"walk")];
        [p performSelector:NSSelectorFromString(@"fly")];
        [p performSelector:NSSelectorFromString(@"pluckStars")];
    }
    return 0;
}

添加一个新类Spaceship,并实现pluckStars方法

#import 
@interface Spaceship : NSObject
- (void)pluckStars;
@end
#import "Spaceship.h"

@implementation Spaceship
- (void)pluckStars
{
    NSLog(@"Spaceship pluckStars");
}
@end

然后,修改我们在Person里面的代码

#import "Person.h"
#import "Plane.h"
#import "Spaceship.h"
#import 
@implementation Person
- (void)eat
{
    NSLog(@"person eat");
}
- (void)walk
{
    NSLog(@"person walk");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"resolveInstanceMethod");
    if (sel == @selector(walk)){
        return class_addMethod([self class], sel, method_getImplementation(class_getInstanceMethod([self class], sel)), [@"V@:" UTF8String]);
    }
    return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@"forwardingTargetForSelector");
    Plane *plane = [[Plane alloc] init];
    if ([plane respondsToSelector: aSelector]) {
        return plane;
    }
    return [super forwardingTargetForSelector: aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"methodSignatureForSelector");
    if (aSelector == @selector(pluckStars)) {
        return [NSMethodSignature signatureWithObjCTypes:"V@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"forwardInvocation");
    if ([anInvocation selector] == @selector(pluckStars)) {
        Spaceship *spaceship = [[Spaceship alloc] init];
        [anInvocation invokeWithTarget:spaceship];
    }else{
        [super forwardInvocation:anInvocation];
    }
}
@end

再次运行,结果如下:

2018-10-29 14:18:42.910585+0800 MsgLine[33073:7581724] person eat
2018-10-29 14:18:42.910858+0800 MsgLine[33073:7581724] person walk
2018-10-29 14:18:42.910888+0800 MsgLine[33073:7581724] resolveInstanceMethod
2018-10-29 14:18:42.910933+0800 MsgLine[33073:7581724] forwardingTargetForSelector
2018-10-29 14:18:42.910956+0800 MsgLine[33073:7581724] Plane fly
2018-10-29 14:18:42.910979+0800 MsgLine[33073:7581724] resolveInstanceMethod
2018-10-29 14:18:42.910996+0800 MsgLine[33073:7581724] forwardingTargetForSelector
2018-10-29 14:18:42.911016+0800 MsgLine[33073:7581724] methodSignatureForSelector
2018-10-29 14:18:42.911040+0800 MsgLine[33073:7581724] resolveInstanceMethod
2018-10-29 14:18:42.911106+0800 MsgLine[33073:7581724] forwardInvocation
2018-10-29 14:18:42.911156+0800 MsgLine[33073:7581724] Spaceship pluckStars

仔细看,当我们调用了[p performSelector:NSSelectorFromString(@"pluckStars")];以后,正常的输出应该是

2018-10-29 14:18:42.910979+0800 MsgLine[33073:7581724] resolveInstanceMethod
2018-10-29 14:18:42.910996+0800 MsgLine[33073:7581724] forwardingTargetForSelector
2018-10-29 14:18:42.911016+0800 MsgLine[33073:7581724] methodSignatureForSelector
2018-10-29 14:18:42.911106+0800 MsgLine[33073:7581724] forwardInvocation
2018-10-29 14:18:42.911156+0800 MsgLine[33073:7581724] Spaceship pluckStars

那么为什么获取了方法签名以后本应该是forwardInvocation的,为什么又多出来了一句

2018-10-29 14:18:42.911040+0800 MsgLine[33073:7581724] resolveInstanceMethod

我试着找了很多消息转发的文章,发现大家其实都有这个问题,但是到这一步直接忽略了这个问题,而官方文档其实也没针对这个问题有解释,除了逆向查看实现,我们还可以查看堆栈信息,首先我们在- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector函数中的返回值处return [NSMethodSignature signatureWithObjCTypes:"V@:@"];下断点,运行程序,单步查看,发现到这里

    0x7fff48a8d704 <+514>:  callq  0x7fff48b9b99e            ; symbol stub for: object_getClass
    0x7fff48a8d709 <+519>:  movq   0x57c918c0(%rip), %r13    ; "_forwardStackInvocation:"
    0x7fff48a8d710 <+526>:  movq   %rax, %rdi
    0x7fff48a8d713 <+529>:  movq   %r13, %rsi
    0x7fff48a8d716 <+532>:  callq  0x7fff48b9b4dc            ; symbol stub for: class_respondsToSelector

其实如果你在+ (BOOL)resolveInstanceMethod:(SEL)sel方法内打印sel方法名的时候,你会发现多出来的forwardInvocation其实就是多出了一个_forwardStackInvocation,为什么多了这个呢,往下看发现

    0x7fff48a8d716 <+532>:  callq  0x7fff48b9b4dc            ; symbol stub for: class_respondsToSelector

到了这里就明白了,在动态实现了查找后,系统会重载respondsToSelector让对外接口查找动态实现函数的时候返回YES,以保证对外接口的行为统一。而respondsToSelector又触发了+ (BOOL)resolveInstanceMethod:(SEL)sel,所以也就多了一句resolveInstanceMethod,但是如果你这样修改main.m

#import 
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        [p performSelector:NSSelectorFromString(@"eat")];
        [p performSelector:NSSelectorFromString(@"walk")];
        [p performSelector:NSSelectorFromString(@"fly")];
        [p performSelector:NSSelectorFromString(@"pluckStars")];
        [p performSelector:NSSelectorFromString(@"pluckStars")];
    }
    return 0;
}

那么可以肯定的是,第二次的[p performSelector:NSSelectorFromString(@"pluckStars")];就不会再多打印一次resolveInstanceMethod了,因为respondsToSelector重载后,已经知道返回值是NO了,也就不需要再次重载了,所以,上面的程序运行完以后,结果就是:

2018-10-29 15:42:23.653136+0800 MsgLine[34473:7749218] person eat
2018-10-29 15:42:23.653406+0800 MsgLine[34473:7749218] person walk
2018-10-29 15:42:23.653429+0800 MsgLine[34473:7749218] resolveInstanceMethod
2018-10-29 15:42:23.653444+0800 MsgLine[34473:7749218] forwardingTargetForSelector
2018-10-29 15:42:23.653464+0800 MsgLine[34473:7749218] Plane fly
2018-10-29 15:42:23.653478+0800 MsgLine[34473:7749218] resolveInstanceMethod
2018-10-29 15:42:23.653490+0800 MsgLine[34473:7749218] forwardingTargetForSelector
2018-10-29 15:42:23.653508+0800 MsgLine[34473:7749218] methodSignatureForSelector
2018-10-29 15:42:23.653530+0800 MsgLine[34473:7749218] resolveInstanceMethod
2018-10-29 15:42:23.653554+0800 MsgLine[34473:7749218] forwardInvocation
2018-10-29 15:42:23.653571+0800 MsgLine[34473:7749218] Spaceship pluckStars
2018-10-29 15:42:23.653586+0800 MsgLine[34473:7749218] resolveInstanceMethod
2018-10-29 15:42:23.653597+0800 MsgLine[34473:7749218] forwardingTargetForSelector
2018-10-29 15:42:23.653607+0800 MsgLine[34473:7749218] methodSignatureForSelector
2018-10-29 15:42:23.653691+0800 MsgLine[34473:7749218] forwardInvocation
2018-10-29 15:42:23.653710+0800 MsgLine[34473:7749218] Spaceship pluckStars

以上内容转载请注明出处,同时也请大家不吝你的关注和下面的赞赏

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

你可能感兴趣的:(iOS消息转发机制分析)