善恶众相

  • 首页

  • 分类

  • 归档

OC运行时源码概览

发表于 2015-06-29 更新于 2019-08-07 分类于 iOS
从代码层次介绍Objective-C动态运行时的加载及运行流程

Object-C作为一门动态语言,它的动态特性是其最基本也是最重要的特性,之所以重要是因为其很多功能及扩展扩展都是基于该特性去完成的。比如:autorelease是如何是实现的,category是如何是实现的,等等这些问题在objc的源代码中我们都能找到答案…

出发准备

在Xcode中对project进行build的过程就是一个编译-链接-生成可执行文件的过程,在Mac OS X系统中每一个可执行文件都是一个Mach-O文件格式。有关Mach-O文件的内容请看这篇文章,当我们运行一个可执行文件时,即在程序运行时会发生什么呢?

在Xcode中随便创建一个simple viewcontroller工程,添加符号断点:

在Symbol一栏中输入_objc_init来添加断点,

成功添加断点后运行程序,程序停在我们的断点处:

熟悉C语言的同学都知道,C语言的程序入口函数是main(),但是由于OC相交于C增加了动态的特性,所以对于OC程序而言,需要在main()函数之前先运行起一个动态系统,即libobjc.A.dylib库的执行,_objc_init函数就是libobjc.A.dylib的程序入口,然后的执行顺序是_os_objct_init->_libdispatch_init->libSystem_initializer->_dyld_start,函数_dyld_stat记录着我们程序的main.m中的main()函数的地址,最终会调起我们的代码去执行。

开始

从objc4-646工程可以看到,最终该工程生成的就是libobjc.A.dylib库,这个库就是objec-C runtime的整体实现,下面我们就从_objc_init函数入手看看这个库到底做了什么?

_objc_init的实现,函数位于/runtime/objc-os.mm:

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
void _objc_init(void)
{
// 保证初始化一次
static bool initialized = false;
if (initialized) return;
initialized = true;

// fixme defer initialization until an objc-using image is found?
// 检查scheme中环境变量设置情况 以便在控制台输出相应选项内容
environ_init();

// 创建线程存储(Thread Specific Data) 详见 tls_create
tls_init();

// 初始化一个读-写锁和递归互斥锁
lock_init();

// 初始化libobjc的异常处理系统
exception_init();

// Register for unmap first, in case some +load unmaps something

/**
通过_dyld_register_func_for_remove_image()方法注册的回调函数(unmap_image),在image运行任何终止器(terminators)之后并且还没有被从内存映射中移除之前被调用.
*/
_dyld_register_func_for_remove_image(&unmap_image);

// 注册一个回调函数,使得当任何一个image变成指定状态时调用.
// 如果“batch”为ture时,当所有images都变成指定状态后,这些images会依次调用注册的回调函数
// 如果“batch”为false时,每当有image变成指定状态时,该image就会立即调用注册的回调函数一次,以此类推

extern void
dyld_register_image_state_change_handler(enum dyld_image_states state, bool batch, dyld_image_state_change_handler handler);
// 参阅 http://www.opensource.apple.com/source/dyld/dyld-97.1/include/mach-o/dyld_priv.h


dyld_register_image_state_change_handler(dyld_image_state_bound,
1/*batch*/, &map_images);

// 调用类的[+load]方法
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}
  • environ_init():检查scheme中环境变量设置情况,以便在控制台输出相应的内容。所有环境变量的宏定义在/runtime/objc-evn.h中

    主要的几个变量及其含义如下:

    OBJC_PRINT_IMAGES:打印加载的映像和库

    OBJC_PRINT_CLASS_SETUP:打印类和分类的处理过程

    具体操作步骤:首先Perduct->Scheme->Edit Scheme

点击 + 号添加环境变量:

最后输入上面的环境变量,值设为YES

运行程序在控制台就会输出环境变量相对应的内容:

  • tls_init():创建线程共享。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef pthread_key_t tls_key_t;
/**
线程存储(Thread Specific Data):
在多线程程序中,所有线程共享程序中的变量.现在有一全局变量,所有线程都可以使用它,改变它的值.
而如果每个线程希望能单独拥有它,那么就需要使用线程存储了.
表面上看起来这是一个全局变量,所有线程都可以使用它,而它的值在每一个线程中又是单独存储的.
这就是线程存储的意义.
最后说一下线程的本质.
其实在Linux中,新建的线程并不是在原先的进程中,而是系统通过一个系统调用clone().
该系统copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数.
不过这个copy过程和fork不一样.
copy后的进程和原先的进程共享了所有的变量,运行环境(clone的实现是可以指定新进程与老进程之间的共享关系,100%共享就表示创建了一个线程).
这样,原先进程中的变量变动在copy后的进程中便能体现出来.
*/
static inline tls_key_t tls_create(void (*dtor)(void*)) {
tls_key_t k;
pthread_key_create(&k, dtor);
return k;
}
  • lock_init():初始化一个读-写锁和递归互斥锁,由于在初始化过程中好多地方都要注意数据竞争,需要用到锁来处理。
  • exception_init():初始化libobjc的异常处理系统。
  • map_images():将类映射到内存中。具体内容下节见。
  • load_images():调用类的[+ load]方法。具体内容写完map_images()再说。

map_images()

我们看看map_images()做了那些处理,跟进代码我们看到最终来到了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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
const char *
map_images_nolock(enum dyld_image_states state, uint32_t infoCount,
const struct dyld_image_info infoList[])
{
static BOOL firstTime = YES;
uint32_t i;
header_info *hi;
header_info *hList[infoCount];
uint32_t hCount;
size_t selrefCount = 0;

……
// Find all images with Objective-C metadata.
hCount = 0;
i = infoCount;
while (i--) {
const headerType *mhdr = (headerType *)infoList[i].imageLoadAddress;

// 将mhdr通过appendHeader函数添加到链表中 由FirstHeader,LastHeader和HeaderCount维护
hi = addHeader(mhdr);
if (!hi) {
// no objc data in this entry
continue;
}
if (mhdr->filetype == MH_EXECUTE) {
// Record main executable's build SDK version
AppSDKVersion = getSDKVersion(hi);

// Size some data structures based on main executable's size
#if __OBJC2__
size_t count;
_getObjc2SelectorRefs(hi, &count);
selrefCount += count;
_getObjc2MessageRefs(hi, &count);
selrefCount += count;
#else
_getObjcSelectorRefs(hi, &selrefCount);
#endif
}

hList[hCount++] = hi;


if (PrintImages) {
_objc_inform("IMAGES: loading image for %s%s%s%s%s\n",
hi->fname,
mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
_objcHeaderIsReplacement(hi) ? " (replacement)" : "",
_objcHeaderOptimizedByDyld(hi)?" (preoptimized)" : "",
_gcForHInfo2(hi));
}
}
....

if (firstTime) {
// 将子类selector数量存到静态变量SelrefCount中;
// 将基类的selector添加到静态结构_objc_selectors中
sel_init(wantsGC, selrefCount);

// 初始化AutoreleasePoolPage用于autorelease机制
// 初始化SideTable用于__weak变量引用管理
arr_init();
}

// 根据设置环境变量的输出走的是objc-runtime-new.mm中的_read_images方法
_read_images(hList, hCount);

firstTime = NO;

return NULL;
}
  • addHeader()最终会调用appendHeader(),它会将mhdr通过appendHeader()函数添加到链表中,并由FirstHeader,LastHeader和HeaderCount去维护。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void appendHeader(header_info *hi)
{
// Add the header to the header list.
// The header is appended to the list, to preserve the bottom-up order.
HeaderCount++;
hi->next = NULL;
if (!FirstHeader) {
// list is empty
FirstHeader = LastHeader = hi;
} else {
if (!LastHeader) {
// list is not empty, but LastHeader is invalid - recompute it
LastHeader = FirstHeader;
while (LastHeader->next) LastHeader = LastHeader->next;
}
// LastHeader is now valid
LastHeader->next = hi;
LastHeader = hi;
}
}
  • _getObjcSelectorRefs()会获取__OBJC segment下section名称为__message_refs的数据,并从中得出selector的数量:
1
2
3
4
5
6
7
SEL *__getObjcSelectorRefs(const header_ *hi, size_t *outCount)
{
unsigned long byteCount = 0;
SEL *data = (SEL *)getsectiondata(hi->mhdr, SEG_OBJC, "__message_refs", &byteCount);
*outCount = byteCount / sizeof(SEL);
return data;
}
  • sel_init():记录selctor数量存储在SelrefCount中;初始化selector列表(NSObject的基类方法)并使用__sel_registerName()方法注册selectors
  • arr_init():初始化AutoreleasePoolPage类实现autorelease机制;初始化SizeTable类,用于对__weak对象的维护。

    AutoreleasePoolPage是实现autorelease的类,具体的autorelease在什么时候去release对象以及何时去release等问题,请看这篇文章
  • _read_images():这里根据设置环境变量的输出,走的是/runtime/objc-runtime-new.mm中的_read_images()方法,具体详情下一节见。

read_images()

/runtime/objc-runtime-new.mm中的_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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/***********************************************************************
* _read_images
* 对链接列表的headers进行初始化处理
* hList指向由hCount个header_info结构组成数组
* 根据环境变量输出一些信息 涉及到future class
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
static BOOL doneOnce;

rwlock_assert_writing(&runtimeLock);

#define EACH_HEADER \
hIndex = 0; \
crashlog_header_name(nil) && hIndex < hCount && (hi = hList[hIndex]) && crashlog_header_name(hi); \
hIndex++

if (!doneOnce) {
doneOnce = YES;

……

// simple view application total = 3305 contant realizing/methodizing calss and category of class
if (PrintConnecting) {
_objc_inform("CLASS: found %d classes during launch", total);
}

// namedClasses (NOT realizedClasses)
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
// 4/3 是NSMapTable(hash table)的负载因子
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotal : total) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTableFromZone(NXStrValueMapPrototype, namedClassesSize,
_objc_internal_zone());

// realizedClasses and realizedMetaclasses - less than the full total
// 类实现 和 元类实现
realized_class_hash =
NXCreateHashTableFromZone(NXPtrPrototype, total / 8, nil,
_objc_internal_zone());
realized_metaclass_hash =
NXCreateHashTableFromZone(NXPtrPrototype, total / 8, nil,
_objc_internal_zone());
}


……

// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
NXMapTable *protocol_map = protocols();
// fixme duplicate protocols from unloadable bundle
for (i = 0; i < count; i++) {
protocol_t *oldproto = (protocol_t *)
getProtocol(protolist[i]->mangledName);
if (!oldproto) {
size_t size = max(sizeof(protocol_t),
(size_t)protolist[i]->size);
protocol_t *newproto = (protocol_t *)_calloc_internal(size, 1);
memcpy(newproto, protolist[i], protolist[i]->size);
newproto->size = (typeof(newproto->size))size;

newproto->initIsa(cls); // fixme pinned
NXMapKeyCopyingInsert(protocol_map,
newproto->mangledName, newproto);
if (PrintProtocols) {
_objc_inform("PROTOCOLS: protocol at %p is %s",
newproto, newproto->nameForLogging());
}
} else {
if (PrintProtocols) {
_objc_inform("PROTOCOLS: protocol at %p is %s (duplicate)",
protolist[i], oldproto->nameForLogging());
}
}
}
}
for (EACH_HEADER) {
protocol_t **protolist;
protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}

// 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;

// hack for class __ARCLite__, which didn't get this above
#if TARGET_IPHONE_SIMULATOR
if (cls->cache._buckets == (void*)&_objc_empty_cache &&
(cls->cache._mask || cls->cache._occupied))
{
cls->cache._mask = 0;
cls->cache._occupied = 0;
}
if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache &&
(cls->ISA()->cache._mask || cls->ISA()->cache._occupied))
{
cls->ISA()->cache._mask = 0;
cls->ISA()->cache._occupied = 0;
}
#endif

realizeClass(cls);
}
}

……

// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
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.
// 将category存储到NXMapTable中 等待类被relized的时候(调用realizeClass方法)添加到类的方法列表中
// 方法的添加顺序是前序添加进类的方法列表中的
// 即:如果原来类的方法是a,b,c,类别的方法是1,2,3,那么插入之后的方法将会是1,2,3,a,b,c
// 也就是说,原来类的方法被category的方法覆盖了,但被覆盖的方法确实还在那里
BOOL classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}

if (cat->classMethods || cat->protocols
/* || cat->classProperties */)
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}

// Category discovery MUST BE LAST to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// category必须最后去输出以避免该线程正在访问时其他线程调用新的category代码
// +load handled by prepare_load_methods()

if (DebugNonFragileIvars) {
realizeAllClasses();
}

#undef EACH_HEADER
}
  • NXMapTable:从上面的代码中可以看出Objcet-C中用于存储类或者协议的结构就是NXMapTable,这是一种hash结构体,有关该结构体的介绍请参阅这篇文章。NSMapTable定义在/runtime/hashtable2.h中。
  • _getObjc2ProtocolList():对协议进行处理
  • relizedClass:实现类
  • addUnattachedCategoryForClass():这个方法我们可以着重看一下,它就是Object-C中将分类加到类的方法列表的核心方法,我们看看这个方法都做了什么:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
header_info *catHeader)
{
rwlock_assert_writing(&runtimeLock);

BOOL catFromBundle = (catHeader->mhdr->filetype == MH_BUNDLE) ? YES: NO;

// DO NOT use cat->cls! cls may be cat->cls->isa instead
NXMapTable *cats = unattachedCategories();
category_list *list;

list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
_calloc_internal(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
_realloc_internal(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (category_pair_t){cat, catFromBundle};
NXMapInsert(cats, cls, list);
}

从上面代码我们可以看出,它是将从Mach-O文件中读取到的分类方法lsit,插入了一个叫category_map得NXMapTable中,该list对应的key就是分类将要添加到的类的名称,下一步的remethodizeClass()方法最终会读取category_map中的list,并通过attachCategoryMethods->attachMethodLists将类的分类的转换成类的方法,更详细的添加过程请参阅这篇文章。

load_images()

最后我们再看一下load_image()函数都做了些什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const char *
load_images(enum dyld_image_states state, uint32_t infoCount,
const struct dyld_image_info infoList[])
{
BOOL found;

recursive_mutex_lock(&loadMethodLock);

// Discover load methods
// 查找load方法
rwlock_write(&runtimeLock);
found = load_images_nolock(state, infoCount, infoList);
rwlock_unlock_write(&runtimeLock);

// Call +load methods (without runtimeLock - re-entrant)
// 调用load方法
if (found) {
call_load_methods();
}

recursive_mutex_unlock(&loadMethodLock);

return nil;
}

通过上面的代码我们知道,load_images()围绕的就是调用类或分类中的[+ load]方法。

由于能力和经验有限,有些地方可能分析的不够深入,更详细的分析以后补充吧!

Mach-O文件格式与otool工具的使用
OC运行时源码细节
  • 文章目录
  • 站点概览
Zrongl

Zrongl

23 日志
3 分类
GitHub E-Mail
  1. 1. 出发准备
  2. 2. 开始
  3. 3. map_images()
  4. 4. read_images()
  5. 5. load_images()
© 2019 Zrongl
不争无尤
|
主题 – NexT.Mist v7.3.0
0%