美文网首页
Runtime第五篇-方法和消息发送

Runtime第五篇-方法和消息发送

作者: lzh_coder | 来源:发表于2017-09-20 17:05 被阅读9次

    先把Method的内存模型摆出来:

    typedef struct objc_method *Method;

    struct objc_method {

    SEL method_name;

    char* method_types;

    IMP method_imp;

    }

    1,SEL-选择器

    typedef struct objc_selector * SEL;

    runtime并没有给出SEL的具体结构。实际上SEL是对方法名的一种映射。它出现的需求在于,方法的执行首先要找到方法Method结构体,在method的链表中,怎样去定位一个结构体。我们看Method的内存结构,一共三个字段。不用分析,只能通过method_name来定位到这个结构体,它是一个SEL,它是由方法名转化得来的。所以,SEL是用来定位Method的,进而执行Method的method_imp。

    Objective-C的这种设计决定了Objective-C这门语言不支持重载。

    SEL的相关操作:

    const char * sel_getName ( SEL sel );

    2,IMP-函数指针

    id (*IMP)(id, SEL, ...)

    3,Method的操作

    3.1 执行一个方法

    id method_invoke ( id receiver, Method m, ... );

    3.2 获取方法名

    SEL method_getName ( Method m );

    3.3 获取Method的IMP

    IMP method_getImplementation ( Method m );

    3.4 获取Method的类型编码

    const char * method_getTypeEncoding ( Method m );

    3.5 获取方法返回值的类型字符串

    char * method_copyReturnType ( Method m );

    3.6 设置Method的imp

    IMP method_setImplementation ( Method m, IMP imp );

    3.7 交换Method的imp

    void method_exchangeImplementations ( Method m1, Method m2 );

    4,消息发送

    Objective-C中的方法调用都是在runtime动态调用的,实际上是执行了objc_msgSend方法。

    objc_msgSend(receiver, selector)

    如果有参数:

    objc_msgSend(receiver, selector, arg1, arg2, ...)

    这个方法会动态查询方法的imp,class_getMethodImplementation,如果正常流程没有查询到,就会返回msg_forward的imp,就会走消息转发流程。

    具体细节是这样的:

    当消息发送给一个对象时,objc_msgSend通过对象的isa指针获取到类的结构体,首先会在类的结构体的cache里面查找selector,如果没有查找到,就去方法链表里面查找方法的selector。如果当前类没有找到selector,则通过类结构体中的superClass指针找到其父类,在父类中依旧是先查找cache,然后查找方法链表,如果找不到,会一直沿着类的继承体系到达NSObject类。一旦定位到selector,函数会就获取到了实现的入口点,并传入相应的参数来执行方法的具体实现。另外,在方法链表里面查询到的方法会被放到cache里面,提高selector的查找速度。如果最终还是找不到,就走消息转发流程。

    5,消息转发

    一般我们不确定一个实例对象是否响应某个方法的时候,我们会使用

    - (BOOL)respondsToSelector:(SEL)aSelector;来确认一下,避免报unrecognized selector exception。

    这里要讲的是,如果不调用respondsToSelector加保护,出现selector定位不到的时候怎么办,它的机制是怎么样的。

    这个机制一共分三步,这一套机制NSObject里面提供了接口:

    1,动态方法解析。--给没有实现的selector提供一种实现。

    + (BOOL)resolveInstanceMethod:(SEL)sel;//给实例selector提供一种实现

    + (BOOL)resolveClassMethod:(SEL)sel;//给类selector提供一种实现。

    eg.

    void functionForMethod1(id self, SEL _cmd) {

    NSLog(@"%@, %p", self, _cmd);

    }

    + (BOOL)resolveInstanceMethod:(SEL)sel {

         NSString *selectorString = NSStringFromSelector(sel);

         if ([selectorString isEqualToString:@"method1"]) {

                class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");

         }

        return [super resolveInstanceMethod:sel];

    }

    2,备用接受者。--更换消息接受者。

    - (id)forwardingTargetForSelector:(SEL)aSelector; //提供一个新的消息接受者。

    基于runtime的这个机制,我们可以做一些松耦合的东西。比如做一个中心类,client调用中心类的方法,中心类通过这一机制进行方法分发。

    3,消息重新转发。--这一步不仅可以更改消息接受者,还可以更改消息的参数。

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

    此外需要重写:

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

    eg.

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

     {

            NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];

            if (!signature) {

                      if ([ARespondClass instancesRespondToSelector:aSelector]) {

                      signature = [ARespondClass instanceMethodSignatureForSelector:aSelector];

                      }

          }

          return signature;

    }

    - (void)forwardInvocation:(NSInvocation *)anInvocation 

    {

           if ([ARespondClass instancesRespondToSelector:anInvocation.selector]) {

                [anInvocation invokeWithTarget:ARespondClassObject];

           }

    }

    在这个例子里面并没有修改NSInvocation,没有修改它的参数。在这一步里面,实际上可以做更加灵活的消息转发,必须追加一个消息参数等等。

    组合和继承是面向对象的一个重要话题。runtime的消息转发机制提供了一种优雅的组合实现机制。对外,client调用中心类的方法,通过performSelector,对内,中心类对消息进行分发。

    相关文章

      网友评论

          本文标题:Runtime第五篇-方法和消息发送

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