【OC源码】Runtime | 二. runtime 怎么实现封装 & 基础数据结构

先明白一件事, Runtime 在做什么, 以及都做了哪些达成目的:
一. 起源 — runtime 要解决什么 & 为什么这样设计

从这里得出, 总共三个点:

  • runtime 在实现封装
  • runtime 在实现消息传递
  • runtime 在实现消息传递的过程中, 用的是具有动态性的方式
    以及一个 OC 的引申:
  • runtime 怎么实现 OC 中 Category, protocol, property 等语言特性
    这里分四篇

目前网上大多数文章, 使用的是已被标记为不可用的 runtime 版本做为讲解
runtime 源码乍一看很难找到入手地方, 里面涉及了不同的宏控制, 同一个结构体多处的定义
我整理一篇 其他: Runtime 源码索引

正文中都直接给出位置, 不再赘述, 后面直接讲述主体

源码中基础数据结构

面向对象最基础的概念: 对象
前面提到过, OC 用的 C/C++ 实现, OC 中的类和对象(不考虑 Category, Property 等情况下).
本质就是用两个结构体类型, 创建的几个变量, 并通过赋值变量内部字段, 使变量产生 “类”和”对象”的关系
我们先看这两个结构体 struct objc_object , struct object_class

ps. objc_object 不直接等同于 OC 的对象 , objc_class 不直接等同于 OC 的类

描述类与对象的结构体

struct objc_object {  
private:  
    isa_t isa;  
public:   
// ...  
// 一系列操作函数  
}  

objc-private.h
通过源码我们可以看到:

  • objc_object 是只有一个字段 isa 的结构体
  • isa 是个指向某个地址的”指针”(新版中是 Tagged Pointer(部分 bit 描述信息, 剩余 bit 存地址), 旧版中就正常指针)

    isa 具体的实现方式, 我写在这里面 其他: Tagged Point 与 isa

objc-runtime-new.h

typedef struct objc_class *Class;  
struct objc_class : objc_object {  
    // Class ISA;  
    Class superclass;  
    cache_t cache;             // formerly cache pointer and vtable  
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags  
	  // .. 一系列操作函数  
}  

通过源码我们可以看到:

  • objc_class 继承于 objc_object (所以他也有一个 isa 字段)
  • objc_class 有一个自己类型的指针, 名为 superclass
  • 剩余 cache 和 bits 字段先不管(这里先着重讨论 类和对象)

编译后的 OC 类与对象

接下来, 我们用 OC 定义一个类(@interface Test : NSObject),并提供一些成员变量、成员函数、类函数.用 clang 编译这些 OC 代码, 看最终生成的结果
clang -rewrite-objc Test.m -o Test.cpp
具体过程见此: 其他: Clang 编译后代码分析

Test.cpp
这里不再分析过程, 直接上结论一: 对于一个 OC 的类(只有成员变量和函数的基本类,其他协议等以后再谈) Test

  • 其内部函数(类函数, 成员函数)都会直接定义成全局静态函数
  • 其他信息用到几个结构体进行描述:
    • struct Test_IMPL : 里面包含其父类的成员变量指针, 自己的成员变量指针, 目前除了计算大小外没看到其他用处
    • struct _ivar_t : 描述成员变量用, 包括位置,大小,名字,类型
    • struct _ivar_list_t : 存放成员变量的集合: 包含_ivar_t的大小, _ivar_t的总数, _ivar_t组成的数组
    • struct _objc_method : 描述函数用(成员及类都是): 包含 SEL, Type Encodings, 函数指针
      (SEL 是什么具体见: 其他: Clang 编译后代码分析, Type Encodings 见 其他: Type Encodings )
    • struct _method_list_t: 存放函数的集合: 包含 _objc_method的大小, 总数, 数组
    • struct _class_ro_t : 存放当前类中,所有自身内部信息用: 包含开始位置,大小,名字, 基础函数_method_list_t,协议,变量_ivar_list_t ,属性
    • struct _class_t : 存放当前类中,自身关系用: 包含 isa, superclass, cache 等
      (isa 是什么具体见: 其他:探究 isa 的指向 )
      其中: OC 中的一个类 Test 会被拆成两部分结构体做描述:
      1. 描述 对象信息 的: struct _class_t OBJC_CLASS_$_Test: 里面包括成员变量, 成员函数, 及关系等
      2. 描述 类信息 的:struct _class_t OBJC_METACLASS_$_Test: 里面包括类函数等
      3. 1 中有个 isa 字段指向 2, 最终描述整个类就是一个 struct _class_t OBJC_CLASS_$_Test

结论二: 对于任意 OC 的”对象”, 都是 struct objc_object 的指针(也在此文中 其他: Clang 编译后代码分析)

  • NSObject, NSObject 子类, NSObject 子类的子类, 本质一样, 都是 struct objc_object 的指针
  • “对象” 与自己的”成员变量”,”成员函数”等都没有从属关系 (objc_object 里就只有一个isa指针)
  • “对象” 的 “成员变量”, 通过 对象地址 + offset 计算出来, 然后用意取值,修改
  • “对象” 的 “成员函数”, 通过 “消息传递” objc_msgSend 机制找到
    1. 成员提供 “对象”本身 或类函数提供 “类名” 用 objc_getClass 对应的 struct objc_class *
    2. 再提供 “函数名” 用sel_registerName 找到 SEL
    3. 最后使用 objc_msgSend 找到对应的函数
    4. “成员函数” 在编译后多出两个入参 “self”, “SEL”. “成员函数” 内部, 通过 ”self” 调用函数
/***  
 * OC 中代码  
 */  
Test *test;  
[test testFunction: 1];  
test->testProperty1 = @(123);  
  
/***  
 * 编译后  
 */  
  
//Test 是 struct objc_object 的别名, 创建一个 struct objc_object 的指针  
typedef struct objc_object Test;    
Test *test;   
  
// "对象"函数本身被定义成全局函数, 并直接增加两个入参  
static void _I_Test_testFunction_(Test * self, SEL _cmd, int value) {  
    printf("TestFunction\n %d",((int (*)(id, SEL))(void *)objc_msgSend)((id)(*(NSNumber **)((char *)self + OBJC_IVAR_$_Test$testProperty1)), sel_registerName("intValue")));  
}  
// "对象" 调用函数变成 objc_msgSend, 类函数带入 objc_getClass("Test"), 成员函数 (id)test  
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)test, sel_registerName("testFunction:"), 1);  
  
  
// "对象" 调用成员变量变成, 直接找到这个变量的地址, 直接做修改  
// 计算成员变量 offset 的代码  
#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)  
extern "C" unsigned long int OBJC_IVAR_$_Test$testProperty1 __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Test, testProperty1);  
// 直接通过"对象"地址 + offset 得到"变量"地址, 然后做修改  
(*(NSNumber **)((char *)test + OBJC_IVAR_$_Test$testProperty1)) = ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), (123));  

到这里, OC 中的”类”与”对象”概念是怎么用 c/c++ 实现已经明确
有一点需要注意, 我们在源码中看到的struct objc_class 不完全与 struct _class_t 等同, 真实的环境中我们可以动态添加函数, 一个类可能有多个 Category 包含多组函数. 这些都是在内部过程中进一步整合后归纳到 struct objc_class 中的

剩余的关于 struct objc_class 缓存, metchod 查找等在 三. runtime 的消息机制 & 围绕消息机制设计的数据结构
剩余的protocol, category 等内容待整理, 但大同小异, 之后的消息传递过程更为重要