关于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

    iOS~runtime理解iOS runtime学习笔记Objective-C Runtime让你快速上手Runt...

  • 关于ios runtime理解

    什么是runtime 说到runtime,根据字面意思就是运行期间。但我觉得首先应该说一下oc到底是个什么东西。首...

  • 多线程cocoachina资源

    iOS:学习runtime的理解和心得

  • 赵熊猫关于iOS的文章索引

    1.Runtime的个人理解 2.Runloop的个人理解 3.iOS - 关于UIView中添加手势监听和tab...

  • 壹、面试复习OC篇之runtime

    暂时copy过来,过后添加自己理解 原文地址:iOS-runtime通篇详解-上 iOS-runtime通篇详解-...

  • IOS学习笔记--RunTime的理解

    IOS学习笔记--RunTime的理解 RunTime的理解 runtime:运行时刻是指一个程序在运行(或者在被...

  • Java中的反射

    反射 今天我来分享下, 我关于Java中反射的理解。如果做过iOS开发的同学应该很清楚iOS里Runtime的黑魔...

  • ios runtime 详解

    前言 在开始之前建议先阅读iOS runtime的基础理解篇:iOS内功篇:runtime 有筒子在面试的时候,遇...

  • iOS runtime实战应用:成员变量和属性

    前言 在开始之前建议先阅读iOS runtime的基础理解篇:iOS内功篇:runtime 有筒子在面试的时候,遇...

  • iOS runtime实战应用:关联对象

    前言 在开始之前建议先阅读iOS runtime的基础理解篇:iOS内功篇:runtime 有筒子在面试的时候,遇...

网友评论

    本文标题:关于ios runtime理解

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