发布于 

关于Runtime的两个用处

iOS开发人员都知道Runtine是比较底层的东西,平时用得少,但是一旦用起来,威力是巨大的,常常有四两拨千斤的用处。

Runtime是什么

运行时刻是指一个程序在运行(或者在被执行)的状态。也就是说,当你打开一个程序使它在电脑上运行的时候,那个程序就是处于运行时刻。在一些编程语言中,把某些可以重用的程序或者实例打包或者重建成为“运行库”。这些实例可以在它们运行的时候被连接或者被任何程序调用。

  • Runtime从字面上理解是运行时的意思,OC是一门动态编程语言,归因于继承和多态,代码的最终实现在运行的时候才最终确定。而Runtime可以通过这种特性,在程序运行前对代码自定义。Runtime是一个开源库,通过这个库,OC中的[target doMethodWith:var1]转换为objc_msgSend(target,@selector(doMethodWith:),var1),下面就来看看这个转换是怎么发生的。
  • 注意使用Runtime需要导入objc/runtime.h头文件

1.类与对象的存储结构

  • 对象的结构:首先了解一点,OC中对象、类、Block这些在运行的时候最终都是已结构体的形式存储的,其中对象的结构体如下:
struct objc_object {
  Class isa  OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;

isa指针:对象的isa指针是一个指向该对象类(Class)的指针,翻译过来就是“是一个”,这个对象“是一个”Class的对象。在代码执行过程中,通过isa指针找到对象的类,在类中存储着许多的信息,包括属性列表和方法列表以及其他信息。

  • 类的结构:在代码执行过程中通过对象的isa指针找到类的地址后,会在类的结构体中寻找执行函数所需要的信息,类的结构体如下:
struct objc_class {
      Class isa  OBJC_ISA_AVAILABILITY;

  #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;  // 方法缓存
      struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议列表
  #endif

  } OBJC2_UNAVAILABLE;

isa指针:类的isa指针指向该类的元类,也就是说我们使用的类其实也是一个对象,在类的结构体中存储实例方法,在元类的结构体中存储类方法。
objc_ivar_list:成员变量列表,存储类的成员变量,可以通过操作这个成员变量列表来添加属性。
objc_method_list:实例方法列表,存储类的实例方法,可以用过实例方法列表来交换方法的实现,自定义方法的实现。
cache:方法缓存,这个缓存的主要作用是将该类使用过的方法缓存起来,我们平时使用xCode编程的方法提示就是缓存在这个cache中。

  • 元类:上面说了,类是元类的对象,类的isa指针指向类的元类,元类中存储着类方法。元类的结构和类是一样的,元类的isa指针指向根元类,根元类的isa指针指向自身。除了isa指针,类和元类的结构体中还有一个super_class指针,指向各自的父类,当方法在本类中找不到时,就会在父类中寻找,值得注意的是:根元类的父类是根类,根类的父类是nil。了解这些关系,就可以了解在代码执行过程中消息发送机制转换的函数如何实现。说到底就是在类的继承体系中找到实现方法的指针,实例方法在类中寻找,类方法在元类中寻找。具体结构如下图所示:

2. [target doMethodWith:var1]是怎么运行的

  • 首先代码运行时会将[target doMethodWith:var1]编译为objc_msgSend(target,@selector(doMethodWith:),var1),然后通过target对象结构体中的isa指针寻找到对象的结构体地址。
  • 在寻找到类的结构体后,如果方法doMethodWith:是实例方法,就在类结构体中的methodLists寻找这个方法来执行,如果没有找到,就会通过类的super_class指针到父类的methodLists去寻找,一直向上,如果在根类没有找到,就会crash;如果是类方法,就会通过类结构体中的isa指针找到元类结构体的地址,在元类的methodLists寻找类方法,如果没有找到也会像上面的父类去寻找,最终没有找到也会crash。
  • var1是执行doMethodWith:方法需要的参数,不必多说。

本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 @shyiuanchen 创建,使用 Stellar 作为主题。