先明白一件事, 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
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会被拆成两部分结构体做描述:- 描述 对象信息 的:
struct _class_t OBJC_CLASS_$_Test: 里面包括成员变量, 成员函数, 及关系等 - 描述 类信息 的:
struct _class_t OBJC_METACLASS_$_Test: 里面包括类函数等 - 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机制找到- 成员提供 “对象”本身 或类函数提供 “类名” 用
objc_getClass对应的struct objc_class * - 再提供 “函数名” 用
sel_registerName找到 SEL - 最后使用 objc_msgSend 找到对应的函数
- “成员函数” 在编译后多出两个入参 “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 等内容待整理, 但大同小异, 之后的消息传递过程更为重要