美文网首页
IOS---消息转发机制

IOS---消息转发机制

作者: silencerZiBo | 来源:发表于2017-06-30 11:19 被阅读94次

    一>形成由来:
    Object-C是在C的基础上进行编写的一门动态程序语言(也称超C)。底层全部是由C语言实现的。所谓的类,转换到底层C的实现,即结构体等相关的代码。相比于静态编译的语言来说,OC在具体执行的时候才会查询到具体的方法指针,从而实现方法。
    而也正是这种OC的动态语言特性造就了两大该语言自身的特色:
    1.runtime
    2.消息转发机制以及消息传递(msg_send)
    本篇幅主要讨论第二种,消息传递与转发机制。
    首先要明确的是,OC的方法是如何调用的。在C、C++等语言中,我们定义了一个函数,然后经由其他函数调用时,相当于效用指向所定义函数的指针地址。通过地址查询到了该函数,并随后经由此具体实现一个方法。而OC其实也是相同的。首先需要了解objc_class的结构。
    我们所创建的每一个类,以及系统原生定义的工具类,都是遵循如下的class结构体所进行创建和存储的,而OC的Class的isa便指向一个类的结构体,其中包含众多该Class的详细信息:

    struct objc_class {
        struct objc_class super_class;  /*父类*/
        const char *name;               /*类名字*/
        long version;                   /*版本信息*/
        long info;                      /*类信息*/
        long instance_size;             /*实例大小*/
        struct objc_ivar_list *ivars;   /*实例参数链表*/
        struct objc_method_list **methodLists;  /*方法链表*/
        struct objc_cache *cache;               /*方法缓存*/
        struct objc_protocol_list *protocols;   /*协议链表*/
    };
    

    由该结构可以看到,Class的isa所指向的结构体中,包含了该类的父类名称、该类的名称、版本信息、方法链表等等。而涉及到该Class的所有函数,是由methodLists方法链表进行保存。而OC中的方法存储是用一一对应的映射关系来进行管理的。如下图结构所示:

    image.png

    如上,方法名和具体的实现地址形成映射的关系。而这里,存储方法名指针selector的是SEL链表,对应存储方法实现地址指针的address的是IMP链表。
    SEL:选择子。即方法名的指针,我们平时调用一个方法,都是通过输入方法名,转化为选择子(sel),然后通过该sel在所属类的方法链表中查询具体的方法的。如果查询到,则使用对应的函数地址(imp),查询到具体的实现函数并执行函数。如果查询不到,则涉及到消息的转发了。调用以下方法的时候,我们是能看出方法在调用时的状态的:

    [self performSelector:<#(SEL)#>];
        @selector(<#selector#>)
    

    了解完了具体的Object-C的方法调用机制以后,我们就能理解消息转发机制能够形成的基础了。之所以留给消息转发实现的空间,就在于函数的具体执行,是在方法调用才从selector中寻找目标SEL,然后找到对应的IMP中的address来执行的。是动态编译的过程。因为,在Class本身查询SEL出现问题时,留出了一定的空间,供人操作改选择子(SEL)在本类动态添加或者其他对象中查询SEL对应的IMP实现方法,从而实现方法,防止报错。

    完整的消息转发机制的过程:


    image.png

    从图上也可以看得出,消息转发总共分为三个阶段,层层递进。其中,到了第三个阶段,便称之为启动了完整的消息转发机制。当然,我们尽希望于在尽可能靠前的方法中,完成函数的处理,从而减少一定的性能消耗。
    首先第一步:类内动态方法解析阶段

    void testFunction(id self,SEL _cmd){
        printf("通过动态解析以及runtime添加的方法,并得到执行");
    }
    //类对象的动态解析函数
    +(BOOL)resolveClassMethod:(SEL)sel
    {
        //获取方法名称
        NSString * selNameStr = [NSString stringWithFormat:@"%@",NSStringFromSelector(sel)];
        //掩饰一个案例:当我们要识别的是一个testFunction方法,并动态添加
        if ([selNameStr isEqualToString:@"testFunction"])
        {
            //为该类动态添加一个方法testFunction处理消息,并执行该方法
            class_addMethod([self class], sel, (IMP)testFunction, "v@:");
            return YES;
        }
        return [super resolveClassMethod:sel];
    }
    //实例对象的动态解析函数
    +(BOOL)resolveInstanceMethod:(SEL)sel
    {
        //获取方法名称
        NSString * selNameStr = [NSString stringWithFormat:@"%@",NSStringFromSelector(sel)];
        //掩饰一个案例:当我们要识别的是一个testFunction方法,并动态添加
        if ([selNameStr isEqualToString:@"testFunction"])
        {
            //为该类动态添加一个方法testFunction处理消息,并执行该方法
            class_addMethod([self class], sel, (IMP)testFunction, "v@:");
            return YES;
        }
        return [super resolveClassMethod:sel];
    }
    

    当第一步完成了SEL的处理,则方法得到执行,并结束。如若没有处理SEL,则进入第二步,启用备援接受者来处理该SEL。
    第二步:启动备援接受者
    第二步和第一步的区别在于,备援接受者方法在处理时可以向其他对象发送该SEL,并通过其他对象执行本对象不可执行的方法。这有别于动态方法解析,给自身动态添加未实现方法。当返回 非self\非nil 时,消息被转给新对象执行。

    -(id)forwardingTargetForSelector:(SEL)aSelector
    {
        NSLog(@"unknow SEL's name is:%@",NSStringFromSelector(aSelector));
    
        Class unknowClass = NSClassFromString(@"TargetClass");
        NSObject * unknowOC = unknowClass.new;
        if (aSelector == NSSelectorFromString(@"TargetClassFunction")) {
            NSLog(@"找到了备援接受者");
            return unknowOC;
        }
        return [self forwardingTargetForSelector:aSelector];
    }
    

    第二步,实质上是使用其他内部对象,来处理本类对象的未知SEL。你也可以利用该特性,营造出多重继承的效果。当第二步返回self、nil时,证明未被查到的SEL还没有找到合适的方法IMP。这是则走到第三步,启动完整的消息转发机制。
    第三步:启动完整的消息转发机制
    走到这一步时,系统会将那个未被处理的SEL封装为NSInvocation对象。里边详细封装了该SEL的所有信息。
    需要注意的是methodSignatureForSelector,和forwardInvocation是配套使用的。如果你不实现方法签名,那么你是调用不到forwardInvocation函数的。这里就和NSInvocation对象本身创建就需要进行方法签名有关。以下是它的结构

    /*  NSInvocation.h
        Copyright (c) 1994-2016, Apple Inc. All rights reserved.
    */
    
    #import <Foundation/NSObject.h>
    #include <stdbool.h>
    
    @class NSMethodSignature;
    
    NS_ASSUME_NONNULL_BEGIN
    
    NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available")
    @interface NSInvocation : NSObject {
    @private
        void *_frame;
        void *_retdata;
        id _signature;
        id _container;
        uint8_t _retainedArgs;
        uint8_t _reserved[15];
    }
    
    + (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;
    
    @property (readonly, retain) NSMethodSignature *methodSignature;
    
    - (void)retainArguments;
    @property (readonly) BOOL argumentsRetained;
    
    @property (nullable, assign) id target;
    @property SEL selector;
    
    - (void)getReturnValue:(void *)retLoc;
    - (void)setReturnValue:(void *)retLoc;
    
    - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
    - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
    
    - (void)invoke;
    - (void)invokeWithTarget:(id)target;
    
    @end
    
    //方法签名
    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        NSString *sel = NSStringFromSelector(aSelector);
        if ([sel rangeOfString:@"set"].location == 0)
        {
            //动态造一个 setter函数
            return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
        } else {
            //动态造一个 getter函数
            return [NSMethodSignature signatureWithObjCTypes:"@@:"];
        }
    }
    //完整消息转发
    -(void)forwardInvocation:(NSInvocation *)anInvocation
    {
        SEL selector = [anInvocation selector];
        if ([company respondsToSelector:selector])
        {
            [anInvocation invokeWithTarget:company];
    }
    

    第三步,相比较于第二步,有一个显著的特点就是:你可以将该方法封装的NSInvocation对象,同时交给多个不同的对象去尝试实现。

    疑问&思考:

    .Q:转发机制的三个方法区别在于什么?为什么要层层递进,而不能使用一个万事大吉?
    A:首先,方法1是在本类中利用runtime动态去做方法添加来处理;方法2通过转发给另外一个对象来进行处理;方法3是通过将这个未处理的方法的所有信息全部封装,创建NSInvocation对象,再开启完整转发机制。方法3相比于方法2,它可以给多个对象做转发,来处理该对象。
    以上是三者的区别。但是,最大的问题我认为还是在于,层层递进之下,性能的消耗也是在不断攀升的。所以,在不考虑具体特定需求的情况下,我们还是遵照性能最优来处理。当然,如果有特定的需求,如多重继承等等,则根据实际情况,在做判断。

    未完成部分:
    1.NSInvocation的使用
    2.消息转发的平时使用案例

    相关文章

      网友评论

          本文标题:IOS---消息转发机制

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