美文网首页iOS干货CoderiOS
iOS内功篇:runtime

iOS内功篇:runtime

作者: 明仔Su | 来源:发表于2016-03-10 19:43 被阅读8538次

    前言

    本来打算写一篇关于runtime的学习总结,无奈长篇大论不是我的风格,就像写申论一样痛苦,加之网上关于tuntime的文章多如牛毛,应该也够童子们学习的了,今天就随便聊聊我的理解吧。

    runtime是什么

    对于初学者,runtime如尼斯湖水怪一样,只存在于传说中,对于开发者,runtime是做好iOS开发,或是深刻掌握Objective C所必需理解的东西。大公司面试都喜欢问:你对runtime熟悉吗?并不是runtime在开发中经常用到,我认为它是OC最核心的部分,只有掌握好它,你才能理解其底层的原理,而不是做一个只会造轮子的码农。要练成盖世神功,需先奠定自身深厚的内功,而tuntime就是iOS开发中的内功。

    那么runtime到底是什么鬼?

    runtime是一个c和汇编写的动态库(感谢Lision的指正),它就像一个小小的系统,将OC和C紧密关联,这个系统主要做两件事 :
    1、封装C语言的结构体和函数,让开发者在运行时创建、检查或者修改类、对象和方法等等。
    2、传递消息,找出方法的最终执行代码。
    

    听起来蛮抽象的,我们来点通俗的吧?没问题~~
    我们先写一句OC的代码

    [zhangsan walkTheDog];
    

    那么在运行时runtime会将它转化成C语言的代码

    objc_msgSend(zhangsan, @selector(walkTheDog));
    

    这个方法就是发送消息的方法,类似这样的方法runtime提供了很多,比如:

    objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount ); // 获取属性列表
    Method * class_copyMethodList ( Class cls, unsigned int *outCount );            // 获取所有方法的数组
    BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );       // 添加方法
    

    那么我们可以利用这些方法干点什么?

    1、遍历对象的属性
    比如,看看zhangsan的有哪些属性(身高:180、年龄:18)
    
    2、动态添加/修改属性,动态添加/修改/替换方法
    比如,修改zhangsan的身高为190、年龄为20,替换walkTheDog方法(变成walkTheBigDog),给他添加一个新方法(walkTheCat)等等
    
    3、动态创建类/对象/协议等等
    比如,创建一个新的对象:lisi
    
    4、方法拦截调用
    比如,给zhangsan发送一个walkTheDog消息,但是zhangsan不知道怎么walk啊(没实现该方法),那我们可以拦截下,给该方法动态添加一个实现,甚至可以讲该方法定向或者打包给lisi(其他对象),让lisi来walk。
    

    以上就是runtime的通俗解释,只是稍微举个例子,更多用法大家可以发挥聪明才智,举一反三。

    方法调用流程

    通俗地讲,调用方法(包含实例方法和类方法)相当于給一个对象发送消息。

    所以,实际上,类本身也是一个对象(关于Class这一块就不再这里展开了)。
    当我们调用一个方法时,是这样的:
    Instance:调用实例方法时,会到对象所属的类的方法列表中查找。
    Class:调用类方法时,会到类的metaClass的方法列表中查找。
    

    下面以实例对象调用方法[blackDog walk]为例描述方法调用的流程:

    1、编译器会把`[blackDog walk]`转化为`objc_msgSend(blackDog,SEL)`,SEL为@selector(walk)。
    
    2、Runtime会在blackDog对象所对应的Dog类的方法缓存列表里查找方法的SEL
    
    3、如果没有找到,则在Dog类的方法分发表查找方法的SEL。(类由对象isa指针指向,方法分发表即methodList)
    
    4、如果没有找到,则在其父类(设Dog类的父类为Animal类)的方法分发表里查找方法的SEL(父类由类的superClass指向)
    
    5、如果没有找到,则沿继承体系继续下去,最终到达NSObject类。
    
    6、如果在234的其中一步中找到,则定位了方法实现的入口,执行具体实现
    
    7、如果最后还是没有找到,会面临两种情况:
    ``(1) 如果是使用`[blackDog walk]`的方式调用方法``
    ``(2) 使用`[blackDog performSelector:@selector(walk)]`的方式调用方法``
    

    第一种情况编译器会报错,第二种需要到运行时才能确定对象能否接收指定的消息,这时候会进入消息转发的流程:

    消息转发流程

    1、动态方法解析
    接收到未知消息时(假设blackDog的walk方法尚未实现),runtime会调用+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)
    在该方法中,我们可以給未知消息新增一个已经实现了的方法。

    void walkFunc(id self, SEL _cmd) {
        //let the dog walk
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        NSString * selString = NSStringFromSelector(sel);
        if ([selString isEqualToString:@"walk"]) {
            class_addMethod(self.class, @selector(walk), (IMP)walkFunc, "@:");
        }
        return [super resolveInstanceMethod:sel];
    }
    

    2、备用接收者
    如果以上方法没有做处理,runtime会调用- (id)forwardingTargetForSelector:(SEL)aSelector方法。
    如果该方法返回了一个非nil(也不能是self)的对象,而且该对象实现了这个方法,那么这个对象就成了消息的接收者,消息就被分发到该对象。
    适用情况:通常在对象内部使用,让内部的另外一个对象处理消息,在外面看起来就像是该对象处理了消息。
    比如:blackDog让女朋友whiteDog来接收这个消息

    - (id)forwardingTargetForSelector:(SEL)aSelector {
        NSString * selString = NSStringFromSelector(aSelector);
        if ([selString isEqualToString:@"walk"]) {
            return self.whiteDog;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    

    3、完整消息转发
    - (void)forwardInvocation:(NSInvocation *)anInvocation方法中选择转发消息的对象,其中anInvocation对象封装了未知消息的所有细节,并保留调用结果发送到原始调用者。
    比如:blackDog将消息完整转发給主人dogOwner来处理

    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        if ([DogOwner instancesRespondToSelector:anInvocation.selector]) {
            [anInvocation invokeWithTarget:self.dogOwner];
        }
    }
    

    4、如果在以上三个方法都没有处理未知消息,则会引发异常。

    后记

    本文主要通俗地描述了runtime的概念,并对其主要作用做了简单的概括,旨在給读者抛砖引玉,runtime的奥妙之处就由读者多多探索学习了。
    初学者需要更深入地学习:
    1、基本概念:Class、Ivar、Method等等
    2、消息转发机制
    3、在<objc/runtime.h>中理解runtime提供的方法和功能
    4、在实际开发中如何灵活运用runtime

    ^_^相对于理论,程序员总是更擅长用代码来表达,博主接下来将陆续更新runtime实战应用系列文章,敬请关注。
    

    传送门:
    iOS runtime实战应用:成员变量和属性
    iOS runtime实战应用:关联对象
    iOS runtime实战应用:Method Swizzling

    相关文章

      网友评论

      • 靠谱的乐乐:通俗易懂,厉害!
      • QihuaZhou:好!求更!
      • kevinLY:学习了,有点挑刺的赶脚,第二段里面最后一行的runtime单词拼错了
      • macfai:赞一个
      • 5036c055ae32:谢谢楼主
      • Raybon_lee:写的不错,值得学习一下 :smile:
      • xxttw:正好药学习runtime 楼主速度更新 希望能写一些实际项目中能够使用的例子
        明仔Su:@Unc1eWang 更新了
      • dfc63a852abb:比较通俗易懂
      • Lision:有几点不敢苟同:
        -1.runtime并不是纯c语言写的库,它是c和汇编写的动态库,部分函数的实现是用汇编语言写的。
        -2.文中的消息转发机制第七点:
        7、如果最后还是没有找到,会面临两种情况:
        ``(1) 如果是使用`[blackDog walk]`的方式调用方法``
        ``(2) 使用`[blackDog performSelector:@selector(walk)]`的方式调用方法``
        第一种情况编译器会报错,第二种需要到运行时才能确定对象能否接收指定的消息,这时候会进入消息转发的流程:
        中有错误,第一种情况[obj func]也会进入消息转发。
        runtime的源码中有performSelector函数的实现,事实是[obj performSelector:SEL]就是调用的objc_msgSend函数,源码如下:
        - (id)performSelector:(SEL)sel {
        if (!sel) [self doesNotRecognizeSelector:sel];
        return ((id(*)(id, SEL))objc_msgSend)(self, sel);
        }
        Lision:@明仔Su :relaxed: 客气
        明仔Su:谢谢指出,我会尽快理清并更新文章
        Lision:@Lision 补充说明:其实文中的两种方法没有实现消息转发都会崩,本质没有区别!
      • 0cf100c3a1dd:挺详细

      本文标题:iOS内功篇:runtime

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