OC底层原理14-消息转发机制

我们在 OC底层原理13-动态方法决议 一文中,分析了动态方法决议,调试的时候发现resolveInstanceMethodresolveClassMethod方法如果在类中重写,但是没有添加方法的时候,这2个方法分别会被调用2次,然后才会崩溃,为什么被调了2次呢?抱着这个疑问开始了源码断点调试方法执行顺序的研究

一、方法慢速查询 -> 动态方法决议 -> 消息转发流程图

消息转发机制.jpg

二、快速消息转发forwardingTargetForSelector

2.1 forwardingTargetForSelector官方文档

forwardingTargetForSelector

2.2 消息快速转发

GomuPerson.m

//- (void)sayNO{ NSLog(@"调用:%s",__func__); }

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
//: -- 这里如果不处理依然会崩溃,并且转发的接受者必须实现了sayNO
        return [GomuBoy alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}

GomuBoy.m
//: -- 实现方法
- (void)sayNO{ NSLog(@"调用:%s",__func__); }

//: -- 调用
GomuPerson *p = [GomuPerson alloc];
[p sayNO];

//: -- 打印
-[GomuPerson forwardingTargetForSelector:] ----- sayNO
调用:-[GomuBoy sayNO]
  • 我们成功的把GomuPerson中没有实现的方法sayNO,转发给毫无关系但是实现了sayNO方法的GomuBoy,有效的防止了方法未实现发送的崩溃

  • 当您只想将消息重定向另一个对象,并且比常规转发快一个数量级时,此功能很有用。

  • 快速速消息转发只能重定向消息的接受者

  • forwardingTargetForSelector可以写在本类或者父类或者它们的分类中,都能起到消息快速转发的作用

三、慢速消息转发methodSignatureForSelector

3.1 methodSignatureForSelector文档

methodSignatureForSelector

3.2 消息慢速转发

//: -- 单独使用此方法也会崩溃,必须配合forwardInvocation使用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
//: -- 注意:这里返回nil,会报错`unrecognized selector sent to instance 0x100752600`
//: -- 返回签名
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//: -- 事务处理,可以重定向当前方法的接收者(GomuBoy),也可以重定向方法(sayCode)
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"%s ----- %@",__func__,NSStringFromSelector(anInvocation.selector));
    if (anInvocation.selector == @selector(sayNO)) {
//: -- 注意: 下面可以不实现,也不会崩溃,但是forwardInvocation方法必须实现
//: -- 下面2种处理方式二选一
//: -- 重定向方法 sayNO-> sayCode
//        anInvocation.selector = @selector(sayCode);
//: -- 重定向消息接受者 GomuPerson -> GomuBoy
        anInvocation.target = [GomuBoy alloc];
//: -- 调用
        [anInvocation invoke];
    }
}

//: -- 调用
GomuPerson *p = [GomuPerson alloc];
[p sayNO];

//: -- 打印
-[GomuPerson methodSignatureForSelector:] ----- sayNO
-[GomuPerson forwardInvocation:] ----- sayNO
调用:-[GomuBoy sayNO]
  • methodSignatureForSelector必须配合forwardInvocation使用,才能防止崩溃

  • unrecognized selector sent to instance错误是由methodSignatureForSelector方法返回nil的时候抛出的

  • forwardInvocation方法里面不处理事务也不会崩溃,只需要配合methodSignatureForSelector实现就能防止崩溃

  • 慢速消息转发不仅可以重新向消息的接受者,也可以重定向消息

  • 慢速消息转发的使用场景:可以对当前没有实现的方法,进行保存,想什么时候转发都可以,即维护一个不立即处理的消息

  • methodSignatureForSelectorforwardInvocation也可以写在本类或者父类或者它们的分类中,都能起到消息慢速转发的作用

四、resolveInstanceMethod、forwardingTargetForSelector、methodSignatureForSelector 比较

4.1 作用

  • resolveInstanceMethod 重定向消息对象 sayNO -> sayCode

  • forwardingTargetForSelector重定向消息接受者 GomuPerson -> GomuBoy

  • methodSignatureForSelector即可以重定向消息对象,也可以重定向消息接受者

4.2 使用场景

  • resolveInstanceMethod:立即调用,并且只需要重定向消息对象

  • forwardingTargetForSelector:立即调用,并且只需要重定向消息接收者

  • methodSignatureForSelector:可以任何时候调用,可以重定向消息对象,也可以重定向消息接收者

4.3 互斥性:只要有一个方法对消息进行了处理,就不会走后面流程

  • 如果resolveInstanceMethod对当前sel进行了处理,重定向了消息对象,就不会再调用forwardingTargetForSelectormethodSignatureForSelector

  • 如果resolveInstanceMethod没有处理,forwardingTargetForSelector重定向了消息接受者,methodSignatureForSelector就不会调用

五、通过方法调用验证上面流程图

//: -- 动态决议,只调方法不实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(sel));
    }
    return [super resolveInstanceMethod:sel];
}

//: -- 消息开始转发,只调方法不实现
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
    }
    return [super forwardingTargetForSelector:aSelector];
}

//: -- 消息慢速转发,只调方法不实现
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
    }
    return [super methodSignatureForSelector:aSelector];
}

//: -- 事务处理,只调方法不实现
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if (anInvocation.selector == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(anInvocation.selector));
    }
}

//: -- 三次机会都没处理,调用这里
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    if (aSelector == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
    }
    [super doesNotRecognizeSelector:aSelector];
}

打印:
+[GomuPerson resolveInstanceMethod:] ----- sayNO
-[GomuPerson forwardingTargetForSelector:] ----- sayNO
-[GomuPerson methodSignatureForSelector:] ----- sayNO
+[GomuPerson resolveInstanceMethod:] ----- sayNO
-[GomuPerson doesNotRecognizeSelector:] ----- sayNO
  • 再次验证了我们上面源码断点分析出的流程正确

六、拓展知识

6.1 监听objc底层消息发送,打印相关调用方法日志

//: -- 扩展:当前方法不在该类中,但是在其他地方有申明
//: -- 扩展声明系统没有提供的方法,才能直接调用
extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        GomuPerson *p = [GomuPerson alloc];
//: -- 用该方法包裹研究对象,打印出来的就是对应的日志
        instrumentObjcMessageSends(YES);
        [p sayNO];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}
  • 日志路径/tmp/msgSends/,如下图msgSends-11981
    日志路径.png
  • 打开日志,会发现我们前面流程图的调用顺序,整个消息转发机制调用方法(msgSend)的流程,如图


    消息转发机制调用方法(msgSend)的流程.png

你可能感兴趣的:(OC底层原理14-消息转发机制)