runtime

作者: GFan | 来源:发表于2021-05-20 15:37 被阅读0次

    一、简介

    OC 是一门动态的语言,而 Runtime 是使用 OC 开发 iOS 应用的一个核心技术。OC 中很多动态的特性都是通过 Runtime 来实现的。

    当一个类进行方法的调用时,本质是利用 Runtime 的消息机制发送一条消息,从而达到调用方法的目的,其底层就是调用了  objc_msgSend 函数。

    想要发送一条消息给某个对象,最多会经历三个阶段,即消息发送动态方法解析消息转发。如果经历这三个阶段都没有找到该消息,那么程序在运行时就会抛出一个错误 :unrecognized selector sent to instance。

    二、消息发送

    当我们给某个对象发送消息时,通过该对象的 isa 找到该对象的类对象。在类对象中的方法缓存中查找有没有该方法,如果有即进行调用。

    如果在类对象的缓存中没有该方法,那就在该类对象存储方法的位置查找方法,如果有该方法,先将该方法加入类对象的方法缓存中去,然后调用该方法。

    如果没有找到该方法,通过 superclass 指针找其父类,先查找父类的方法缓存中有没有该方法,如果有该方法那么就进行调用,如果缓存中没有该方法,那么就从父类的方法列表中查找方法,如果找到该方法,先缓存到类对象的方法缓存中,再进行调用

    如果元类对象的父类还是没找到该方法,就找父类的父类依次类推,重复步骤3。如果最终都没有找到该方法就进行第二个阶段即动态方法解析。

    三、动态方法解析

    当消息发送阶段完成后仍然没有找到对应的方法,那么就会来到动态方法解析阶段。动态方法解析可以帮助我们拦截到消息发送阶段未实现的方法。在动态方法解析提供的方法中,利用 runtime 动态的添加某个方法。当消息发送阶段调用的方法没有找到的时候,来调用我们动态添加的方法。

    下面以实例方法为例进行说明:

    我们定义一个类,该类中只有方法的声明而没有实现。

    @interface SomeClass : NSObject

    - (void)foo;

    @end

    因为是实例方法,所以我们在 SomeClass 的实现文件中需要重写 resolveInstanceMethod 方法,在该方法中来实现动态方法解析。

    假设当我们调用 foo 方法时候,因为 foo 方法未被实现,来到动态方法解析阶段,我们让其调用 bar 方法的实现。

    + (BOOL)resolveInstanceMethod:(SEL)sel {    // 判断方法名是否为 foo   

     if (sel == @selector(foo)) { // 获取被添加的方法        

    Method method = class_getInstanceMethod(self, @selector(bar));// 获取方法实现//        

    IMP imp = class_getMethodImplementation(self, @selector(bar));     

    IMP imp = method_getImplementation(method); // 获取 TypeEncoding        

    const char *types = method_getTypeEncoding(method);// 动态添加方法        

    class_addMethod(self, sel, imp, types);                

    return YES;    

    }     

       return [super resolveInstanceMethod:sel];

    }

    利用 runtime 的 class_addMethod 函数动态的添加方法

    第1个参数:如果是添加实例方法,传入类对象,如果是添加类方法传入元类对象

    第2个参数:被动态添加方法的方法名称

    第3个参数:动态添加方法的方法实现也就是函数地址

    第4个参数:动态添加的方法的参数和返回值的 typeEncoding

    动态解析阶段添加完方法后,会重新执行一遍消息发送的流程。

    如果没有实现动态方法解析,那么就会来到第三个阶段消息转发阶段

    四、消息转发

    当前2个阶段都没有找到对应的消息,最终就会来到消息转发阶段,我们还是以实例方法进行举例说明:

    首先会调用 - (id)forwardingTargetForSelector:(SEL)aSelector 方法,在该方法中进行消息的转发,将该消息转发到其他类,让其他类处理该消息。

    - (id)forwardingTargetForSelector:(SEL)aSelector {    

    if (aSelector == @selector(foo)) {        

    return [[OtherClass alloc] init];    

    }   

     return [super forwardingTargetForSelector:aSelector];

    }

    // OtherClass@interface OtherClass : NSObject

    - (void)foo;@end  @implementation OtherClass

    - (void)foo {    NSLog(@"%s", __func__);

    }@end

    创建一个 OtherClass 的类,该类中声明并实现了 foo 方法

    forwardingTargetForSelector 方法中返回了 OtherClass 的实例对象,意味着我们会将消息交给 OtherClass 去处理,此时就会调用该类中的 foo 方法。

    如果 forwardingTargetForSelector 方法返回 nil 或者压根就没有实现,消息转发阶段会调用其他方法来进行处理。

    首先调用的是 methodSignatureForSelector 方法,该方法返回一个 NSMethodSignature 对象,如果返回值不为 nil,那么他将会调用 forwardInvocation 方法,并且传入一个 NSInvocation 对象,该对象封装了方法调用所必须的条件。

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

    if (aSelector == @selector(foo)) {     

    Method method = class_getInstanceMethod([OtherClass class], aSelector);

    const char *types = method_getTypeEncoding(method);   

    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:types];        

    return signature;    

    }        

    return [super methodSignatureForSelector:aSelector];

    }

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

    [anInvocation invokeWithTarget:[[OtherClass alloc] init]];    

    // 假如 types 随便传的,anInvocation 封装的内容就毫无意义 

     // 我们可以随意进行任何操作    

    OtherClass *oc = [[OtherClass alloc] init]; 

       [oc bar];

    }

    如果我们想要用 anInvocation 来处理消息,那么在 methodSignatureForSelector 方法中 types 一定要传对,关于 types 如何表示的我们可以查看苹果的官方文档Type Encoding

    如果我们不想利用 anInvocation 处理消息,types 其实就可以随便传.

    相关文章

      网友评论

        本文标题:runtime

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