美文网首页
iOS 消息转发机制

iOS 消息转发机制

作者: 我嘞giaogiao | 来源:发表于2020-02-19 15:03 被阅读0次

    (一)对象的消息传递机制 objc_msgSend()

    这叫做“给某个对象发送某条消息”。消息有“名称”或“选择子(selector)”之说。消息可以接受参数,而且还可以有返回值。

    id returnValue = [someObject messgeName:parameter];
    

    本例中,someObject叫做方法调用者,也叫做接受者(receiver)。messageName:是方法名,也叫做选择子(selector)。选择子与参数合起来叫做”消息“(message)。在运行时,编译器会把上面这个格式的方法调用转化为一条标准的C语言函数调用,该函数就是鼎鼎有名的objc_msgSend(),该函数是消息objc里在运行时传递机制中的核心函数,其原型如下:

    void objc_msgSend(id self, SEL cmd, ...)
    多个参数按照顺序排列
    

    根据原型转换后:

    id returnValue = objc_msgSend(someObject,  @selector(messageName:), parameter)
    

    执行这条语句的时候,向一个对象发送消息时,首先会在对象类的cache,method list以及父类对象的cache,method list依次查找SEL对应的IMP
    (cache文末有解释-方法缓存)

    如果没有找到,并且实现了动态方法决议机制就会决议。如果没有实现动态决议机制或者决议失败且实现了消息转发机制。就会进入消息转发流程。否则程序Crash.

    消息具体传递流程:

    objc_msgSend()函数会依据接受者(调用方法的对象)的类型和选择子(方法名)来调用适当的方法。

    • 接收者会根据isa指针找到接收者自己所属的类,然后在所属类的”方法列表“(method list)中从上向下遍历。如果能找到与选择子名称相符的方法,就根据IMP指针跳转到方法的实现代码,调用这个方法的实现。
    • 如果找不到与选择子名称相符的方法,接收者会根据所属类的superClass指针,沿着类的继承体系继续向上查找(向父类查找),如果能找到与名称相符的方法,就根据IMP指针跳转到方法的实现代码,调用这个方法的实现。
    • 如果在继承体系中还是找不到与选择子相符的方法,此时就会执行”消息转发(message forwarding)“操作。

    (二)消息转发流程

    消息转发分为两个阶段。第一阶段叫做“动态方法解析(dynamic method resolution)”,或者叫“动态方法决议”。第二阶段涉及到“完整的消息转发机制(full forwarding mechanism)”,或者叫“完整的消息转发原理”。

    • 动态方法决议

    字面上我的理解是:消息在发送过程中进行判断到底这消息该由谁接收

    一:询问是否有动态添加方法来进行处理
    Objective C 提供了一种名为动态方法决议的手段,使得我们可以在运行时动态地为一个 selector 提供实现。我们只要实现

    + (BOOL)resolveInstanceMethod:(SEL)selector;
    + (BOOL)resolveClassMethod:(SEL)selector;**
    

    具体代码:

    //People.m
    void speak(id self, SEL _cmd){
        NSLog(@"Now I can speak.");
    }
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        NSLog(@"resolveInstanceMethod:  %@", NSStringFromSelector(sel));
        if (sel == @selector(speak)) {
            class_addMethod([self class], sel, (IMP)speak, "V@:");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    
    + (BOOL)resolveClassMethod:(SEL)name  
    {  
        NSLog(@" >> Class resolving %@", NSStringFromSelector(name));  
          
        return [super resolveClassMethod:name];  
    }
    
    • 完整的消息转发

    完整的消息转发又分为两个阶段,第一阶段称为备援接受者(replacement receiver),第二阶段才是启动完整的消息转发机制。

    1.备援接收者

    询问别人是否可以帮助实现一下

     (id)forwardingTargetForSelector:(SEL)selector;
    

    具体代码:

    - (id)forwardingTargetForSelector:(SEL)aSelector {
        NSLog(@"forwardingTargetForSelector:  %@", NSStringFromSelector(aSelector));
        Bird *bird = [[Bird alloc] init];
        if ([bird respondsToSelector:aSelector]) {
            return bird;
        }
        return [super forwardingTargetForSelector: aSelector];
    }
    // Bird.m
    - (void)fly {
        NSLog(@"I am a bird, I can fly.");
    }
    

    方法参数代表未知的选择子,返回值为备援接受者,若当前接受者能找到备援接受者,就直接返回,这个未知的选择子将会交由备援接受者处理。如果找不到备援接受者,就返回nil,此时就会启用“完整的消息转发机制”。

    2.完整的消息转发

    - (void)forwardInvocation:(NSInvocation *)invocation;
    

    核心实现代码:

    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        NSLog(@"forwardInvocation: %@", NSStringFromSelector([anInvocation selector]));
        if ([anInvocation selector] == @selector(code)) {
           Monkey *monkey = [[Monkey alloc] init];
            [anInvocation invokeWithTarget:monkey];
        }   
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSLog(@"method signature for selector: %@", NSStringFromSelector(aSelector));
        if (aSelector == @selector(code)) {
            return [NSMethodSignature signatureWithObjCTypes:"V@:@"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    

    看完核心代码还觉得慌,一脸雾水,具体怎么用呢?代码怎么写?写在哪里?一切都准备在后面,不要慌,继续往下看,待着疑问看下去

    实现此方法时,如果发现调用操作不应该由本类处理,则需要沿着继承体系,调用父类的同名方法,这样一来,继承体系中的每个类都有机会处理这个调用请求,直至rootClass,也就是NSObject类。如果最后调用了NSObject的类方法,那么该方法还会继而调用”doesNotRecognizeSelector:“以抛出异常,此异常表明选择子最终也未能得到处理。消息转发到此结束。

    最后消息未能处理的时候,还会调用到

    - (void)doesNotRecognizeSelector:(SEL)aSelector
    

    我们也可以在这个方法中做些文章,避免掉crash,但是只建议在线上环境的时候做处理,实际开发过程中还要把异常抛出来。

    完整消息转发实例代码:

    代码例子:
    
    viewController中创建TestModel
    
        TestModel * model = [[TestModel alloc]init];
        [model testMethod];
    
    
    TestModel.h
    
       -(void)testMethod;
    
    TestModel.m
    
       -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
        {
          if (aSelector == @selector(testMethod))
          {
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];
           }
          return nil;
      }
    
    -(void)forwardInvocation:(NSInvocation *)anInvocation
     {
        if (anInvocation.selector == @selector(testMethod))
        {
            TestModelHelper1 *h1 = [[TestModelHelper1 alloc] init];
            TestModelHelper2 *h2 = [[TestModelHelper2 alloc] init];
            [anInvocation invokeWithTarget:h1];
            [anInvocation invokeWithTarget:h2];
        }
     }
    
    
    
    @implementation TestModelHelper1
    -(void)testMethod
    {
        NSLog(@"i am TestModelHelper1");
    }
    @end
    
    
    
    @implementation TestModelHelper2
    -(void)testMethod
    {
        NSLog(@"i am TestModelHelper2");
    }
    @end
    
    运行可看到消息转发日志
    

    ps:方法缓存

    通篇下来,发现调用一个方法并不像我们想的那么简单,更不像我们写的那么简单,一个方法的执行其实底层需要很多步骤。正因如此,objc_msgSend()会将调用过且匹配到的方法缓存在”快速映射表(fast map)“中,快速映射表就是方法的缓存表。每个类都有这样一个缓存。所以,即便子类实例从父类的方法列表中取过了某个对象方法,那么子类的方法缓存表中也会缓存父类的这个方法,下次调用这个方法,会优先去当前类(对象所属的类)的方法缓存表中查找这个方法,这样的好处是显而易见的,减少了漫长的方法查找过程,使得方法的调用更快。同样,如果父类实例对象调用了同样的方法,也会在父类的方法缓存表中缓存这个方法。

    同理,如果用一个子类对象调用某个类方法,也会在子类的metaclass里缓存一份。而当用一个父类对象去调用那个类方法的时候,也会在父类的metaclass里缓存一份。

    相关文章

      网友评论

          本文标题:iOS 消息转发机制

          本文链接:https://www.haomeiwen.com/subject/smeafhtx.html