美文网首页
iOS 消息转发objc_msgSend()

iOS 消息转发objc_msgSend()

作者: 学不来的凡人 | 来源:发表于2021-03-20 07:21 被阅读0次

    OC中的方法调用, 其实都是转换为 objc_msgSend() 函数的调用;
    消息机制: 给方法调用者发送消息

    - (void)resolvePlay:(NSString *)ball{
        NSLog(@"%@",NSStringFromSelector(_cmd));
    }
    //方法(实现了直接调用,没有实现走->方案1动态方法解析)
    -(void)play{
        NSLog(@"play");
    }
    //方案1 (动态方法解析, 添加方法)(添加方法成功后,直接调用添加的方法,否则走->方案2消息转发))
    //+ (BOOL)resolveClassMethod:(SEL)sel {
    //    if (sel == @selector(test)) {
    //        // 第一个参数是object_getClass(self) 元类对象
    //        class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
    //        return YES;
    //    }
    //    return [super resolveClassMethod:sel];
    //}
    +(BOOL)resolveInstanceMethod:(SEL)sel{
        class_addMethod([self class], sel, class_getMethodImplementation([Student class], @selector(resolvePlay:)), method_getTypeEncoding(class_getInstanceMethod([Student class],  @selector(resolvePlay:))));
    
        return YES;
    }
    //方案2 (消息转发)(返回调用方法的target,直接target调用方法,否则走->方案3消息转发调用)
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        return [Student new];
    
    }
    //方案3 (方案三的两个方法 方法签名&转发调用)
    //方法签名(方法签名不为nil则走转发调用)
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    //关于生成签名的类型"v@:"解释一下。每一个方法会默认隐藏两个参数,self、_cmd,self代表方法调用者,_cmd代表这个方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表返回值为void,@表示self,:表示_cmd。
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        
    }
    //转发调用
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        [anInvocation invokeWithTarget:[Student new]];
        
    }
    
    

    补救方案

    首先, 调用方法时, 系统会查看这个对象能否接收这个消息 (查看这个类有没有这个方法, 或者有没有实现这个方法。)

    如果不能, 就会调用下面这几个方法, 给你“补救”的机会, 你可以先理解为几套防止程序crash的备选方案, 我们就是利用这几个方案进行消息转发;

    注意, 前一套方案实现, 后一套方法就不会执行。

    如果这几套方案你都没有做处理, 那么 objc_msgSend() 最后找不到合适的方法进行调用, 会报错 unrecognized selector sent to instance

    方案1 (动态方法解析, 添加方法):

    // 首先,系统会调用resolveInstanceMethod或resolveClassMethod 让你自己为这个方法动态增加实现。动态添加方法

    • (BOOL)resolveInstanceMethod:(SEL)sel;
    • (BOOL)resolveClassMethod:(SEL)sel;
      方案2 (消息转发):

    // 如果不对resolveInstanceMethod做任何处理, 系统会来到方案二, 走forwardingTargetForSelector方法,我们可以返回其他实例对象, 实现消息转发。

    • (id)forwardingTargetForSelector:(SEL)aSelector;
      方案3 (消息转发调用):

    // 如果不实现 forwardingTargetForSelector, 系统就会调用方案三的两个方法 方法签名&转发调用

    • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    • (void)forwardInvocation:(NSInvocation *)anInvocation;
    WeChat4244acffd9caf8e3522fe4ab19ff9feb.png image.png image.png

    作为抽象类不能直接使用 NSProxy ,它不提供初始化的方法,并且在接收到它没有响应的任何消息时引发异常,因此只能继承并在具体的子类提供初始化或创建方法,并实现 forwardInvocation:methodSignatureForSelector:

    所以其实只要实现了这两个方法即可,至于其它的一些方法,未实现均会走到消息转发这一步,若是实现其它譬如respondsToSelector:也无妨

    • NSProxyNSObject 的消息传递的不同
        NSObject 收到消息会先去缓存列表查找SEL,若是找不到,就到自身方法列表中查找,依然找不到就顺着继承链进行查找,依然找不到的话,那就是 unknown selector,进入消息转发程序
        1. +(BOOL)resolveInstanceMethod: 其返回值为Boolean类型,表示这个类是否通过class_addMethod新增一个实例方法用以处理该 unknown selector,也就是说在这里可以新增一个处理 unknown selector的方法,若不能,则继续往下传递(若 unknown selector是类方法,那么调用 +(BOOL)resolveClassMethod:
        2. - (id)forwardingTargetForSelector: 这是第二次机会进行处理 unknown selector,即转移给一个能处理 unknown selector的其它对象,若返回一个其它的执行对象,那消息从 id objc_msgSend ( id self, SEL op, ...) 重新开始,若不能,则返回 nil,并继续向下传递,最后的一次消息处理机会(3 与 4 配套)
        3. - (NSMethodSignature *)methodSignatureForSelector: 返回携带参数类型、返回值类型和长度等的 selector 签名信息 NSMethodSignature对象,Runtime 内部会基于 NSMethodSignature 实例构建一个NSInvocation 对象,作为回调- (void)forwardInvocation:的入参
        4. - (void)forwardInvocation:这一步可以对传进来的 NSInvocation 进行一些操作,它主要在对象之间或者应用程序之间存储和转发消息(命令模式的实现),灵活性很高,譬如修改 target 、参数、甚至返回值,有兴趣可以去了解下NSInvocation
        对于 NSProxy 就没有这么复杂了,接收到 unknown selector 后,直接回调- (NSMethodSignature *)methodSignatureForSelector:- (void)forwardInvocation:, 消息转发过程简单的很多

      • 基于以上,实现上也很简单,就是将具体接收消息的实际对象保存在容器中,并按顺序让它们接收消息即可
    /**
     Normal forwarding 的第一步,也是消息转发的最后一次机会--这个针对NSObject, 对于 NSProxy 未实现立马走这里
     消息获得函数的参数和返回值类型,即返回一个函数签名
    
     @param sel selector 方法选择子
     @return NSMethodSignature 函数签名
             返回nil,Runtime 则会发出 -doesNotRecognizeSelector: 消息,程序 crash
             返回了NSMethodSignature,Runtime 就会创建一个 NSInvocation 对象并发送 -forwardInvocation: 消息给目标对象
     */
    - (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
        for (id obj in self.targets) {
            if ([obj respondsToSelector:sel]) {
                return [obj methodSignatureForSelector:sel];
            }
        }
        return [super methodSignatureForSelector:sel];
    }
    
    
    /**
    可以在 -forwardInvocation: 里修改传进来的 NSInvocation 对象,然后发送 -invokeWithTarget: 消息给它,传进去一新的目标执行
    
     @param invocation 对一个消息的描述,包括 selector 以及参数等信息
     */
    - (void)forwardInvocation:(NSInvocation *)invocation {
        BOOL invoked = NO;
        for (id obj in self.targets) {
            if ([obj respondsToSelector:invocation.selector]) {
                [invocation invokeWithTarget:obj];
                invoked = YES;
            }
        }
        if (!invoked) {
            [super forwardInvocation:invocation];
        }
    }
    
    

    OC中存在这么一个默默无闻的类NSProxy,填补了"多继承"这个空白区.

    相关文章

      网友评论

          本文标题:iOS 消息转发objc_msgSend()

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