关于ios runtime理解

作者: Patrick_p | 来源:发表于2017-02-15 17:46 被阅读0次

    什么是runtime

    说到runtime,根据字面意思就是运行期间。
    但我觉得首先应该说一下oc到底是个什么东西。首先,它是一门编程语言;其次,它是基于C语言,在其基础上面增加了面向对象的特性。最后重点的是,oc使用的是“消息结构”,而并非“函数调用”。
    关键性的区别就是:基于消息结构的的语言,运行时所执行的代码是由运行环境来决定的,而不是实在编译期间决定的,这一点就衍生出了runtime机制。
    简单来说runtime就是在运行的期间去选择到底是去使用哪一个方法,而不是在编译期间决定死了。打个通俗的比方,一个人从同一个起点到同一个目的地,如果乘公交车的话,线路是死的,公交只会按照既定的公交线路来走。如果是自己开车的话,可以随便选择自己随性所欲的路线,最终到达目的地就ok了。乘公交的方式,就好比编译期间决定;自己开车的方式就好比运行期间决定。

    预备知识

    oc中对象的定义

    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    

    oc中id对象的定义

    struct objc_object {
        Class isa ;
    } *id;
    

    oc中Class的定义

    // 类
    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;  //指向metaClass(元类)
    
    #if !__OBJC2__
        Class super_class                       OBJC2_UNAVAILABLE;  // 父类
        const char *name                        OBJC2_UNAVAILABLE;  // 类名
        long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0 => 序列化支持
        long info                               OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识
        long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小
        struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 该类的成员变量链表
        struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表
        struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存;优化methodLists查找
        struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表
    #endif
    } OBJC2_UNAVAILABLE;
    
    typedef struct objc_class *Class;
    

    关于元类(metaClass):
    class结构体中存放的是类的元数据(metadata),其结构体中的首个变量也是isa指针,很显然说明了Class本身也是一个OC对象。
    同时调用类方法就是向该类发送消息,会在该方法的方法列表中寻找此方法。

    函数调用原型

    void objc_msgSend(id self, SEL cmd, ...)
    
    

    上面函数调用原型是一个参数可变的函数,可以接受两个或者两个以上的参数。
    第一个参数代表接收者,第二个参数代表SEL类型,后面的参数就是消息中的附带的参数,按照原顺序依次在后。

    下面正式开始

    1.理解objc_msgSend的作用

    知道了上面的信息后,第一步要明白oc调用机制,也就是上面所说的消息调用机制,至于调用原型,参考上面的函数调用原型。
    oc调用对象的方法就是如此,用oc的术语来说,叫做“传递消息”。所以在整个oc开发中,这一点一定要铭记于心。
    比如我们在oc中调用方法是这样写的

    [someObj doSomeThing:parameter];
    

    但编译器在看到此条消息后,会将其转化成标准的C语言函数调用。如下:

    objc_msgSend(someObj, @selector(doSomeThing:), parameter);
    

    当oc开始调用方法的时候,objc_msgSend方法会根据后面的参数来匹配具体的方法。匹配的时候,回去该接收者所属类中的方法列表(methodLists)中去查找。如果查找到了,则跳转到方法中去实现。如果找不到的话,就进行消息转发操作(后面会讲消息转发)。
    如果每次调用都来实现类似的操作的话,那么性能肯定会浪费。所以,objc_msgSend会将匹配的结果缓存起来,下次会从缓存里面取。

    2.消息转发机制

    上面讲了消息的传递机制,但是如果碰到无法解读的消息之后呢?此时oc就会开启消息转发。
    先看看转发的几个方法:
    1.当前类处理

    + (Bool)resolveInstanceMethod:(SEL)selector
    
    + (Bool)resolveClassMethod:(SEL)selector
    

    2.转发到其它类来处理

    - (id)forwardingTargetForSelector:(SEL)selector
    

    3.转发到其它类来处理

    - (void)forwardingInvocation:(NSInvocation*)invocation
    

    关于消息转发的话,总体上就是三步。
    首先由resolveInstanceMethod来处理,如果成功则继续处理找到的方法。如果返回false,则继续向下转发。
    接下来是第二次机会,由forwardingTargetForSelector:来处理,同样的,成功则返回找到的方法。如果返回为nil的话,则继续向下转发。
    还无法处理,就到forwardingInvocation:。到了这一步只能启用完整的消息转发了。会首先创建一个NSInvacation对象,将那条未处理的相关消息(sel,target,参数)全部封装到里面。触发了NSInvacation对象时,就由oc的消息派发系统来调用forwardingInvocation:。当发现某个调用操作不应该由此类来处理的时候,那么就调用超类的同名方法。这样的话继承体系中的每个类都有机会处理该调用请求,直到NSObject。如果最后调用了NSObject的方法,那么该方法会调用“doesNotRecognizeSelector:”来抛出异常信息,结果显示此方法未能得到处理。

    消息转发的流程图如下

    runtime使用

    当我们了解了oc的消息机制后,肯定会疑惑,runtime有何用处?
    下面会说明runtime使用的两个方法,但是一定要慎用,因为可能造成未知的错误。
    1.method swizzling,俗称“黑魔法”。
    2.关联对象(Associated Object)

    1.method swizzling

    正式因为oc的动态绑定,所以我们才可以使用method swizzling黑魔法。
    类方法会将相应的sel名称映射到相关的方法实现上,这些方法均已函数指针的形式来表示,该指针叫做IMP。原型如下:

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

    根据下面这张图,我们来看看映射对应关系:

    这种映射关系是在运行期间选择的,所以我们可以通过一些方法来新增,交换其中映射关系,使本该调用imp1的lowercaseString方法从而实现调用了uppercaseString方法。调用后的映射关系图如下:

    方法实现的获取函数如下:

    Method class_getInstanceMethod(Class aClass, SEL aSelector)
    

    方法实现的交换函数如下:

    void method_exchangeImplementations(Method m1, Method m2);
    

    新增一个方法的函数如下:

    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    
    

    通过上面3个方法我们就可以来实现运行期间方法的改变了。
    代码示例,就直接上Mattt大神的代码了,如下:

    #import "UIViewController+swizzling.h"
    #import <objc/runtime.h>
    
    @implementation UIViewController (swizzling)
    
    //load方法会在类第一次加载的时候被调用
    //调用的时间比较靠前,适合在这个方法里做方法交换
    + (void)load{
        //方法交换应该被保证,在程序中只会执行一次
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
    
            //获得viewController的生命周期方法的selector
            SEL systemSel = @selector(viewWillAppear:);
            //自己实现的将要被交换的方法的selector
            SEL swizzSel = @selector(swiz_viewWillAppear:);
            //两个方法的Method
            Method systemMethod = class_getInstanceMethod([self class], systemSel);
            Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
    
            //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
            BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
            if (isAdd) {
                //如果成功,说明类中不存在这个方法的实现
                //将被交换方法的实现替换到这个并不存在的实现
                class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
            }else{
                //否则,交换两个方法的实现
                method_exchangeImplementations(systemMethod, swizzMethod);
            }
    
        });
    }
    
    - (void)swiz_viewWillAppear:(BOOL)animated{
        //这时候调用自己,看起来像是死循环
        //但是其实自己的实现已经被替换了
        [self swiz_viewWillAppear:animated];
        NSLog(@"swizzle");
    }
    
    @end
    

    在一个自己定义的viewController中重写viewWillAppear

    - (void)viewWillAppear:(BOOL)animated{
        [super viewWillAppear:animated];
        NSLog(@"viewWillAppear");
    }
    

    想看效果的自己跑起来试试。

    2.关联对象(Associated Object)

    一把我们都是直接在程序里面直接添加对象,但万一那段代码不是我们自己写的,或者我们接触不到具体代码怎么办,但是我们又想新增一个对象?这个时候,就可以使用关联对象了。

    我们先来了解关联对象的基本知识。
    1.对象关联类型

    关联类型 等效的@property属性
    OBJC_ASSOCIATION_ASSIGN assign
    OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic,retain
    OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic,copy
    OBJC_ASSOCIATION_RETAIN retain
    OBJC_ASSOCIATION_COPY copy

    2.用给定的键和策略为某对象设置关联对象值

    //关联对象
    void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    
    

    3.用给定的键从某对象中获取对应的关联对象值

    //获取关联的对象
    id objc_getAssociatedObject(id object, const void *key)
    
    

    4.移除指定对象的全部关联对象

    //移除关联的对象
    void objc_removeAssociatedObjects(id object)
    

    要注意的是,设置关联对象的时候,如果想让两个建匹配到同一个值,那么意味着两者的指针必须完全相同。简单来说,就是设置关联对象的建的时候,用静态全局变量做建就行了。

    具体使用的示例参考别人写的这篇文章,讲得很清楚了。iOS runtime实战应用:关联对象

    参考资料

    《effective Objective-C 2.0》

    相关文章

      网友评论

        本文标题:关于ios runtime理解

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