美文网首页OC语法iOS Developerruntime相关
我眼中的runtime(消息转发)

我眼中的runtime(消息转发)

作者: luodezhao | 来源:发表于2016-01-27 16:59 被阅读532次
    runtime可以帮助我们实现一些oc层的api达不到的功能。那就先需要了解一下。

    一、消息转发

    oc中的动态特性,就是他在运行的时候,才能确定某些东西。比如实现方法这个过程。实际上是一个发送消息的过程。这个消息,也许是由接受者执行,也可能是由开发者设定的其他对象执行。消息与方法的绑定,也是运行时才确定.

    消息的发送过程是这样的:
    1.通过对象的isa指针找到它的类
    2.在类的method list 中找到这个方法(根据SEL来找,方法的唯一辨识)
    3.如果class中没有这个方法,就沿着这个类的isa指针 指向它的superclass去找
    4.一旦找到,则执行这个方法的IMP(函数指针,指向方法执行的首地址)

    但是,如果每次都要这么找的话,没有必要每次都在methodlist中循环查找,所以,一旦找到了这个方法,就存为class_cache,下一次就先在这里找,所以这一步应在执行在上文步骤1的前面。


    如果找不到,而开发者没有做任何处理。那么自然会出现 unrecognized selector sent to instance的错误
    其实在这个异常出现之前,会有三个步骤来转发这个消息,如果我们不做任何一个动作,则会异常。

    1.动态的给这个消息添加一个实现方法

    对象在接收到未知的消息时,会调用
    + (BOOL)resolveInstanceMethod:(SEL)sel ;如果是类方法,则是+ (BOOL)resolveClassMethod:(SEL)sel
    在该方法中,我们可以将实现了的方法添加到这个消息里面
      + (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(w)) {
        class_addMethod([self class], sel, (IMP)w, "v@:@");
    //找到了方法然后添加了实现,返回yes。
       return YES;
    }
    return [super resolveInstanceMethod:sel];
    }
    void w(id self,SEL _cmd, NSString * name){
    NSLog(@"%@",name);
    }
    

    这里需要理解一下class_addMethod
    先看一下官方文档怎么说

    官方对这个方法的描述

    首先,
    第一个参数,是要添加的类,自然要传[self class]
    第二个参数 name,是方法的名称,也就是SEL的类型,用@selector可以获取
    第三个参数imp,也就是要给这个方法添加的实现函数,文档里面说了,这个函数必须要接受两个参数,一个是self,一个是_cmd
    第四个参数:types,对imp的描述。至少有三个参数:第一个是返回类型,第二个是self,第三个是sel类型的_cmd,列子中用的是“v @:@”v代表,返回类型void
    ,@代表idleix,:代表sel类型,第二个@代表nsstring类型。
    这里面的转换类型,可以在官方文档中搜Type Encodings查找

    2,如果在上一步没有做任何处理,runtime会执行这个方法,尝试把这个消息发给另一个对象来执行。

    - (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(setBackgroundColor:)) {
        return label;
    }
    return [super forwardingTargetForSelector:aSelector];
    

    }
    这一步只合适于我们将消息转发到另一个能处理该消息的对象,但是不能对该消息做任何处理

    3,如果上一步还不能处理,则会走这个方法

    - (void)forwardInvocation:(NSInvocation *)anInvocation 
    //抛出异常之前,会给对象发送这个消息forwardInvocation
    //anInvocation这个参数包括selector,目标(target)和参数
    - (void)forwardInvocation:(NSInvocation *)anInvocation 
    //抛出异常之前,会给对象发送这个消息forwardInvocation
    这个参数包装了原始消息和对应的参数
      SEL sel = anInvocation.selector;
       if ([label respondsToSelector:sel]) {
      [anInvocation invokeWithTarget:label];把消息转发给label
       }else {
      [self doesNotRecognizeSelector:sel];
      }
      }
    

    用这个方法的时候,我们必须要重写另一个方法:
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    因为上一个方法中的参数使用从这个方法创建的NSInvocation对象,所以必须要重写方法。
    - (NSMethodSignature *)methodSignatureForSelector: (SEL)aSelector {
    NSMethodSignature *s = [super methodSignatureForSelector: aSelector];
    if (!s ) {
    s = [label methodSignatureForSelector:aSelector];
    }
    return s;
    }
    这里是需要重新给method签名,method里面包三个对象:一个sel,一个method_types,一个是imp,sel是根据方法名和参数类型生成唯一识别,method_type包括了消息的所有参数类型(包括两个隐藏参数:一个self,一个_cmd也就是SEL类型),imp就是函数指针。

    消息转发的过程就是这样的。

    下面介绍一下上面提到的SEL,IMP,Method

    SEL

    SEL是表示一个方法的selector指针,oc会根据不同方法的名字,参数序列,生成唯一的标识,也就是这个指针的地址,所以就不难理解为什么oc中不能定义参数不同,但是方法名相同的方法了。

    所以我们现在知道了,SEL是一个指向方法的指针。 对于我们而言,也就是这个方法的函数名(唯一辨识);
    我们可以通过@selector()或者NSSelectorFromString()得到这个指针;

    IMP

    上文提到,IMP是一个函数指针,指向方法实现的首地址。通过SEL能够找到对应的IMP。

    Method

    Method表示方法,包含
    一个SEL
    一个 method_types
    一个 IMP
    这里就相当于SEL和IMP之间有了映射。

    所以现在不难了解开头说的,消息与方法的绑定(objc_msgSend(receiver,selector,参数···)),也是运行时才确定
    1,首先找到这个selector对应的方法实现。因为不同的类中对同一方法有不同的实现,需要根据接受者找到确切的实现。
    2,找到了SEL之后,找到IMP,然后将接受者对象本身,以及SEL本身和其他参数传给他
    3,将IMP的返回作为这个绑定操作的返回。
    所以也不难理解,为什么[self class]和[super class]会打印出self这个子类。因为在super中调用的这个class方法,传入的第一个参数self是子类。

    相关文章

      网友评论

        本文标题:我眼中的runtime(消息转发)

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