Runtime初探(一)

什么是 Runtime

苹果对 Runtime 的解释:

The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objective-C language, and as such is linked to by all Objective-C apps. Objective-C runtime library support functions are implemented in the shared library found at /usr/lib/libobjc.A.dylib.

Objective-C运行时是一个运行时库,它支持Objective-C语言的动态属性,因此所有的Objective-C应用程序都链接到它。Objective-C运行时库支持函数是在/usr/lib/libobjc.A.dylib的共享库中实现的。

本篇文章分析使用的 runtime 为 objc4-756.2

isa 是什么

首先来看下NSObject头文件的定义。

1
2
3
4
5
typedef struct objc_class *Class;

@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}

可以得出NSObject内部保存了一个类型为objc_class的结构体指针。接下来去runtime源码中寻找objc_class的定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};

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

class_rw_t *data() {
return bits.data();
}
......
......

可以看到有isasuperclass这两个成员变量。那么isasuperclass是做什么的呢。上经典图。
图片

首先需要了解instanceclassmeta-class他们之间的关系。需要先知道这些。
1.instanceclass的具象化,内部只保存值。
2.class保存着所有instance的描述,例如:成员变量的类型和函数地址等。
2.meta-class保存着所有class的描述,例如:函数地址。

所以isasuperclass的存在就是为了完成面向对象的继承思想,那么接下来调用方法的过程就很好解释了。

1.如果是一个`instance`调用了函数
1.1 依据 instance 的 isa 指针找到 class
1.2 在 class 上找函数地址,如果未找到则通过superclass指针去父类中寻找,直至找到root class。如果还未找到,会报错。后面会有说消息转发的机制。

2.如果是一个`class`调用了函数
2.1 依据 class 的 isa 指针找到 meta-class
2.2 在 meta-class 上找函数地址,如果未找到则通过superclass指针去父类中寻找,直至找到root meta class。如果还未找到,会报错。后面会有说消息转发的机制。

KVO 实现分析

Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象 Object 时,KVO 机制动态创建一个新的名为:NSKVONotifying_Object 的新类,该类继承自对象 Object 的本类,且 KVO 为 NSKVONotifying_Object 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。
KVO 为子类的观察者属性重写调用存取方法的工作原理在代码中相当于:

1
2
3
4
5
-(void)setName:(NSString *)newName{
[self willChangeValueForKey:@"name"]; //KVO 在调用存取方法之前总调用
[super setValue:newName forKey:@"name"]; //调用父类的存取方法
[self didChangeValueForKey:@"name"]; //KVO 在调用存取方法之后总调用
}

那么如何验证这个原理呢,首先,创建一个 Test 类集成于 NSObject

1
2
3
4
5
6
@interface Test : NSObject
@property (assign, nonatomic) int age;
@end
@implementation Test

@end

通过 runtime 来输出一个对象建立KVO之前之后的类对象地址和元类对象地址来做比对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
self.test1 = [[Test alloc] init];
self.test1.age = 1;

self.test2 = [[Test alloc] init];
self.test2.age = 2;


NSLog(@"test1添加KVO监听之前 - %@ %@",
object_getClass(self.test1),
object_getClass(self.test2));
NSLog(@"test1添加KVO监听之前 - %p %p",
[self.test1 methodForSelector:@selector(setAge:)],
[self.test2 methodForSelector:@selector(setAge:)]);

// 给test1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.test1 addObserver:self forKeyPath:@"age" options:options context:@"123"];

NSLog(@"test1添加KVO监听之后 - %@ %@",
object_getClass(self.test1),
object_getClass(self.test2));
NSLog(@"test1添加KVO监听之后 - %p %p",
[self.test1 methodForSelector:@selector(setAge:)],
[self.test2 methodForSelector:@selector(setAge:)]);
NSLog(@"类对象 - %@ %@",
object_getClass(self.test1),
object_getClass(self.test2));

得到的输出结果如下

1
2
3
4
5
test1添加KVO监听之前 - Test Test
test1添加KVO监听之前 - 0x1056ed8e0 0x1056ed8e0
test1添加KVO监听之后 - NSKVONotifying_Test Test
test1添加KVO监听之后 - 0x7fff257223da 0x1056ed8e0
类对象 - NSKVONotifying_Test Test

可以发现,在 Test 对象被监听前 Test 对象的类对象是 Test(0x1056ed8e0。在) 类。 Test 对象被监听之后,系统为其创建了一个新类 NSKVONotifying_Test(0x7fff257223da) 并将其 isa 指针指向了新类 NSKVONotifying_Test。

Category 和 extension 有什么不同

首先新建一个 xcode 工程。添加如下代码

1
2
3
4
5
6
7
8
9
@interface Test : NSObject
@end
@implementation Test
@end

@interface Test (Ex1)
@end
@implementation Test (Ex1)
@end

通过clang将代码转成cpp文件

1
clang -rewrite-objc Test+Ex1.m -o Test+Ex1.cpp

这是查看Test+Ex1.cpp文件会发现多了如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};

struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_Test;

static struct _category_t _OBJC_$_CATEGORY_Test_$_Ex1 __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Test",
0, // &OBJC_CLASS_$_Test,
0,
0,
0,
0,
};
static void OBJC_CATEGORY_SETUP_$_Test_$_Ex1(void ) {
_OBJC_$_CATEGORY_Test_$_Ex1.cls = &OBJC_CLASS_$_Test;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_Test_$_Ex1,
};
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_Test;
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_Test_$_Ex1,
};

通过代码发现,clang帮我们生成了一个类型为_category_t名称为_OBJC_$_CATEGORY_Test_$_Ex1的结构体,并将结构体的第一个参数name设置成为了分类的名称。由此可以推断分类的数据结构体的类型是_category_t,而一个类的结构体是_class_t。接下来会通过runtime来分析生成的这个类型_category_t的结构是如何使用的。

runtime 启动加载

Objective-C的运行是依赖于runtime库,runtime与其他库一样,都是通过dyld动态加载进来的。首先打开runtime源码中的objc-os.mm文件并找到_objc_init这个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;

// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

通过注释可以得到_objc_init这个函数是引导程序初始化用的。这里主要关注的函数是这个_dyld_objc_notify_register。但是它的定义和实现不在runtime这个开源库中,如果要看实现可以查看dylb的源代码dyld源码
接下来分析下三个参数map_imagesload_imagesunmap_image

map_images

首先来看下源码实现

1
2
3
4
5
6
7
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}

通过代码发现主要的实现逻辑是在map_images_nolock中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
mhCount:当前dyld加载的OC的类库的数量
mhPaths:OC类库的文件地址
mhdirs:每个OC库文件的文件信息
*/
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
// Find all images with Objective-C metadata.
hCount = 0;

// Count classes. Size various table based on the total.
int totalClasses = 0; //用来标识类的数量
int unoptimizedTotalClasses = 0;
{
uint32_t i = mhCount;
while (i--) {
const headerType *mhdr = (const headerType *)mhdrs[i];
/*
1.totalClasses unoptimizedTotalClasses 引用传参 用来记录类的个数
2.返回的是 header_info,用来计算mhPaths[i]这个地址里有多少个类
*/
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
if (!hi) {
// no objc data in this entry
continue;
}
hList[hCount++] = hi;
}
}

if (hCount > 0) {
// 加载类 分类 协议 的地方
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}

firstTime = NO;
}

接下来来分析_read_images是如何实现的,_read_images大致可以分为四个部分,分别为加载类对象、加载协议对象、实现类对象、添加分类。接下来我们逐步分析。一下为去除部分非主流程的伪代码。

加载类对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
// 加载类对象,将类对象存放到 gdb_objc_realized_classes 这个 map 中
for (EACH_HEADER) {
// 获取数据段中 _objc_classlist 类型的变量
classref_t *classlist = _getObjc2ClassList(hi, &count);
if (! mustReadClasses(hi)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();

for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
// 添加进入 gdb_objc_realized_classes 这个 map 中
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

if (newCls != cls && newCls) {
// Class was moved but not deleted. Currently this occurs
// only when the new class resolved a future class.
// Non-lazily realize the class below.
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
加载协议对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Discover protocols. Fix up protocol refs.
// 获取数据段中 _objc_protolist 类型的变量,添加进入到全局的 protocol_map 中。
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->isPreoptimized();
bool isBundle = hi->isBundle();

protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
实现类对象
1
2
3
4
5
6
7
8
9
10
11
12
13
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// 记录这个类已经被创建过
addClassTableEntry(cls);
// 设置 cls 的 superclass 和 date 并添加 methods properties protocols 以及 category_list,这里添加的 category_list 是通过 unattachedCategoriesForClass 来获取的。
realizeClassWithoutSwift(cls);
}
}
添加分类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Discover categories. 
//从程序的数据段寻找__objc_catlist类型的变量。
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();

for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);

if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}

// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// 如果是对象函数 直接添加进入到类中
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
}

if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
// 如果是类函数 添加进元类中
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
}

load_images

在_objc_init 函数中,dyld_register_image_state_change_handler 将 load_images 作为回调函数注册给dylib,所以,当镜像的状态变化时,会回调load_images函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.

// 快速查找所有的类和分类是否有 +load 函数
if (!hasLoadMethods((const headerType *)mh)) return;

recursive_mutex_locker_t lock(loadMethodLock);

// Discover load methods
{
// 用于记录 哪些类和分类需要调用 +load 函数
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}

// Call +load methods (without runtimeLock - re-entrant)
// 将记录后的类和分类 统一调用 +load 函数
call_load_methods();
}