zoukankan      html  css  js  c++  java
  • ios 消息跳转处理

    一.消息转发流程

    当向Objective-C对象发送一个消息,但runtime在当前类及父类中找不到此selector对应的方法时,消息转发(message forwarding)流程开始启动。

    1. 动态方法解析(Dynamic Method Resolution或Lazy method resolution)
      向当前类(Class)发送resolveInstanceMethod:(对于类方法则为resolveClassMethod:)消息,如果返回YES,则系统认为请求的方法已经加入到了,则会重新发送消息。
    2. 快速转发路径(Fast forwarding path)
      若果当前target实现了forwardingTargetForSelector:方法,则调用此方法。如果此方法返回除nil和self的其他对象,则向返回对象重新发送消息。
    3. 慢速转发路径(Normal forwarding path)
      首先runtime发送methodSignatureForSelector:消息查看Selector对应的方法签名,即参数与返回值的类型信息。如果有方法签名返回,runtime则根据方法签名创建描述该消息的NSInvocation,向当前对象发送forwardInvocation:消息,以创建的NSInvocation对象作为参数;若methodSignatureForSelector:无方法签名返回,则向当前对象发送doesNotRecognizeSelector:消息,程序抛出异常退出。
      Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MessageInterceptor test]: unrecognized selector sent to instance 0x9589830'

    二.动态解析(Lazy Resolution)

    runtime发送消息的流程即查找该消息对应的方法或IMP,然后跳转至对应的IMP。有时候我们不想事先在类中设置好方法,而想在运行时动态的在类中插入IMP。这种方法是真正的快速”转发”,因为一旦对应的方法被添加到类中,后续的方法调用就是正常的消息发送流程。此方法的缺点是不够灵活,你必须有此方法的实现(IMP),这意味这你必须事先预测此方法的参数和返回值类型。

    @dynamic属性是使用动态解析的一个例子,@dynamic告诉编译器该属性对应的getter或setter方法会在运行时提供,所以编译器不会出现warning; 然后实现resolveInstanceMethod:方法在运行时将属性相关的方法加入到Class中。

    respondsToSelector:instancesRespondToSelector:方法被调用时,若该方法在类中未实现,动态方法解析器也会被调用,这时可向类中增加IMP,并返回YES,则对应的respondsToSelector:的方法也返回YES。

    三.快速转发(Fast Forwarding)

    runtime然后会检查你是否想将此消息不做改动的转发给另外一个对象,这是比较常见的消息转发情形,可以用较小的消耗完成。
    快速转发技术可以用来实现伪多继承,你只需编写如下代码

    - (id)forwardingTargetForSelector:(SEL)sel { return _otherObject; }

    这样做会将任何位置的消息都转发给_otherObject对象,尽管当前对象与_otherObject对象是包含关系,但从外界看来当前对象和_otherObject像是同一个对象。
    伪多继承与真正的多继承的区别在于,真正的多继承是将多个类的功能组合到一个对象中,而消息转发实现的伪多继承,对应的功能仍然分布在多个对象中,但是将多个对象的区别对消息发送者透明。

    四.慢速转发(Normal Forwarding)

    以上两者方式是对消息转发的优化,如果你不使用上述两种方式,则会进入完整的消息转发流程。这会创建一个NSInvocation对象来完全包含发送的消息,其中包括target,selector,所有的参数,返回值。

    在runtime构建NSInvocation之前首先需要一个NSMethodSignature,所以它通过-methodSignatureForSelector:方法请求。一旦NSInvocation创建完成,runtime就会调用forwardInvocation:方法,在此方法内你可以使用参数中的invocation做任何事情。无限可能…
    举个例子,如果你想对一个NSArray中的所有对象调用同一个方法,而又不想一直写循环代码时,想直接操作NSArray时,可这样处理:

    @implementation NSArray (ForwardingIteration)
    
        - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
        {
            NSMethodSignature *sig = [super methodSignatureForSelector:sel];
            if(!sig)
            {
                for(id obj in self)
                    if((sig = [obj methodSignatureForSelector:sel]))
                        break;
            }
            return sig;
        }
    
        - (void)forwardInvocation:(NSInvocation *)inv
        {
            for(id obj in self)
                [inv invokeWithTarget:obj];
        }
    
        @end

    然后就可以这样使用

    [(NSWindow *)windowsArray setHidesOnDeactivate:YES];

    不过不建议这样使用,因为若NSArray实现了此方法,就不会进入转发流程。实现这种功能的一种比较好的方法是使用NSProxy。

    五.方法声明

    虽然上述机制可以转发当前类中没有实现的方法,但发送消息时仍然需要知道每个消息的方法签名,否则就会有编译器告警。可以通过category来声明转发消息的方法。

    六.使用消息转发在子类中处理Delegate消息

    当继承一个具有delgate的类,而又需要在子类中处理某些delegate消息,而又不影响对正常Delegate消息的调用时,需要如何处理呢?
    一种方法是将子类对象设为自身的delegate,而将外部设置的delegate存储到另一个参数中。在子类中实现所有的delegate方法,处理子类中需要处理的delegate消息,而将子类中不处理的delegate消息再发送到外部delegate。这种方法的缺点在于实现繁琐,在子类中需要实现所有delegate方法,尽管大部分delegate消息又直接转给了外部delegate处理。
    另一种比较优雅的方式是使用消息转发,创建一个proxy类,将proxy类设置为父类的delegate,在proxy中分别将消息转发给子类或外部Delegate。
    比如,创建一个UISCrollView的子类可使用如下代码
    MessageInterceptor.h

    @interface MessageInterceptor : NSObject {
        id receiver;
        id middleMan;
    }
    @property (nonatomic, assign) id receiver;
    @property (nonatomic, assign) id middleMan;
    @end

    MessageInterceptor.m

    @implementation MessageInterceptor
    @synthesize receiver;
    @synthesize middleMan;
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        if ([middleMan respondsToSelector:aSelector]) { return middleMan; }
        if ([receiver respondsToSelector:aSelector]) { return receiver; }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    - (BOOL)respondsToSelector:(SEL)aSelector {
        if ([middleMan respondsToSelector:aSelector]) { return YES; }
        if ([receiver respondsToSelector:aSelector]) { return YES; }
        return [super respondsToSelector:aSelector];
    }
    
    @end

    MyScrollView.h

    #import "MessageInterceptor.h"
    
    @interface MyScrollView : UIScrollView {
        MessageInterceptor * delegate_interceptor;
        //...
    }
    
    //...
    
    @end

    MyScrollView.m

    @implementation MyScrollView
    
    - (id)delegate { return delegate_interceptor.receiver; }
    
    - (void)setDelegate:(id)newDelegate {
        [super setDelegate:nil];
        [delegate_interceptor setReceiver:newDelegate];
        [super setDelegate:(id)delegate_interceptor];
    }
    
    - (id)init* {
        //...
        delegate_interceptor = [[MessageInterceptor alloc] init];
        [delegate_interceptor setMiddleMan:self];
        [super setDelegate:(id)delegate_interceptor];
        //...
    }
    
    - (void)dealloc {
        //...
        [delegate_interceptor release];
        //...
    }
    
    // delegate method override:
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        // 1. your custom code goes here
        // 2. forward to the delegate as usual
        if ([self.delegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
            [self.delegate scrollViewDidScroll:scrollView];
        }
    }
    
    @end

    MessageInterceptor对象会自动将将子类中实现的delegate消息转发给子类,而将其他所有delegate消息转发给外部设置的delegate对象。

    在MessageInterceptor中除了实现forwardingTargetForSelector:方法外,还实现了respondsToSelector:方法,因为UIScrollView在发送delegate消息之前会首先使用respondsToSelector:判断delegate是否实现了该方法,而转发的消息对respondsToSelector:也应返回YES。

    参考:
    Friday Q&A 2009-03-27: Objective-C Message Forwarding
    Objective-C Runtime Programming Guide – Dynamic Method Resolution
    Objective-C Runtime Programming Guide – Message Forwarding
    Intercept obj-c delegate messages within a subclass
    Hacking Block Support Into UIMenuItem
    NSObject Class Reference
    NSObject Protocol Reference
    NSInvocation Class Reference
    NSMethodSignature Class Reference

  • 相关阅读:
    Job for vsftpd.service failed because the control process exited with error code
    Linux 调优方案, 修改最大连接数-ulimit
    vsftpd配置文件详解
    Linux下TCP最大连接数受限问题
    vsftp限制FTP用户只能访问自己的目录
    linux YUM常用 命令
    Linux 系统sudo命令
    部分有关 广告联盟作弊 与反作弊资料收集
    Boosted Tree
    如何将数据转换libsvm格式文件
  • 原文地址:https://www.cnblogs.com/tinkl/p/3412104.html
Copyright © 2011-2022 走看看