Runtime

Runtime是iOS动态特性的重要体现,也是iOS的内部核心之一。几乎所有的oc代码,底层都以基于runtime。其源码由c和汇编语言编写。

本文以及接下来的runtime的文章都是基于objc-750版本的源码

前沿

一些常见而重要的关键字:

typedef struct objc_object *id;
typedef struct objc_class *Class;
typedef struct objc_selector *SEL;
typedef struct objc_method *Method;

id

id 就是 OC 对象,它是指向某个类的实例的指针。定义如下:

struct objc_object { 
    Class isa; 
    };

通过源码可以看到 id 就是 objc_object *的别名,为一个指向objc_object 结构体的指针,而 objc_object 实际上就包含了 isa 指针和一些方法。根据 isa 指针就可以找到对象所属的类,isa 是一个非常重要而核心的东西,oc的动态方法都基于它实现。

KVO 的实现机理就是将被观察对象的 isa 指针指向一个中间类而不是真实类型

class

class 才是 oc 对象或方法最终的集合,上文 isa 就是 class 的指针, class 的定义如下:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

objc_class 可以看到,一个运行时类中关联了它的父类指针(super_class)、类名(name)、成员变量(ivars)、方法(methodLists)、缓存(cache)以及附属的协议(protocols)。

其中 objc_ivar_listobjc_method_list 分别定义 如下:

struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
} 
struct objc_method_list {
    struct objc_method_list * _Nullable obsolete             OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}

objc_ivar_list 和 objc_method_list 分别保存了成员和方法的数组,由此可见我们其实可以动态的给一个 class 添加方法, 这也是 catgory 的实现思路。 运行时,category 会将定义的方法放入到方法列表的前头,因而会覆盖原来的方法(实际上没有覆盖, 只是 category 的方法在列表前, 被优先调用了)。

需要注意的是 objc_class 也包含了 isa 指针, 这说明 objc_class 也是 oc 对象, 其所属的类称作元类(Meta Class),Meta Class 表述了类对象本身所具备的元数据。

SEL

SEL(selector)是方法选择器,它对方法名进行包装,以便找到对应的方法实现。它的数据结构是:

typedef struct objc_selector *SEL;

关于objc_selector的源码暂时未找到,不过依然可以大致理解其作用和特点:

selector是个映射到方法的 C 字符串,你可以通过 Objc 编译器器命令@selector()来获取一个方法的选择器。

不同类中相同名字的方法所对应的 selector 是相同的,由于变量的类型不同,所以不会导致它们调用方法实现混乱。

Method

Method 代表类中某个方法的类型:

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}

typedef struct objc_method *Method;

由此可见 Method 保存了SEL 和 IMP 的联系。

Ivar

Ivar 是表示成员变量的类型:

struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}

typedef struct objc_ivar *Ivar;

其中 ivar_offset 是基地址偏移字节, 我们对 ivar 的访问就可以通过 对象地址 + ivar偏移字节的方法。

` 个人理解:这里侧面告诉我们不能动态添加成员,因为会造成成员列表里的成员 偏移地址 混乱。`

IMP

IMP本质是一个函数指针,是由编译器生成的。当发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。

如果得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法。

Cache

typedef struct objc_cache *Cache

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。

Runtime 系统会把被调用的方法存到 Cache 中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。就像计算机组成原理中 CPU 绕过主存先访问 Cache 一样。

Category

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

从源码基本可以看出我们平时使用categroy的方式,对象方法,类方法,协议,和属性(不是成员)都可以找到对应的存储方式。并且我们发现分类结构体中是不存在成员变量的,因此分类中是不允许添加成员变量的。

由于method是基于函数名称和函数指针的联系的,所以Category中名称相同而返回值不同的方法依然会发生覆盖,具体可参考 struct objc_method 的定义

isa

关于isa 指针先看一张图: image_isa 先清楚几个概念:

  • 实例的实例方法函数存在类结构体中
  • 类方法函数存在metaclass结构体中
  • metaClass 由汇编或者编译器生成(猜想)
  • object-c 通过isa和metalClass的关系链来实现消息灵活传递机制 isa 本质是一个联合体,其构成如下:
    union isa_t 
    {
      Class cls;
      uintptr_t bits;
      struct {
           uintptr_t nonpointer        : 1;//->表示使用优化的isa指针
           uintptr_t has_assoc         : 1;//->是否包含关联对象
           uintptr_t has_cxx_dtor      : 1;//->是否设置了析构函数,如果没有,释放对象更快
           uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针
           uintptr_t magic             : 6;//->固定值,用于判断是否完成初始化
           uintptr_t weakly_referenced : 1;//->对象是否被弱引用
           uintptr_t deallocating      : 1;//->对象是否正在销毁
           uintptr_t has_sidetable_rc  : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
          uintptr_t extra_rc          : 19;  //->存储引用计数
      };
    };
    

资料

苹果runtime的代码已经开源, 现在可以在网上找到各个版本的源代码,甚至还可以自己编译runtime来debug,例如以下为我编译的runtime工程:

Runtime: 苹果runtime源码编译后的工程