Bootstrap initialization
/***********************************************************************
* _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);
}
dyld
自举引导调用_objc_init
,这个函数很清晰:
首先会进行各单位初始化,如环境变量初始化、tls (thread local storage)
线程本地缓存初始化、静态构造器初始化、锁初始化、异常处理初始化;
其次要保存映射所有的镜像文件(map_images
)、保存加载这些镜像文件函数指针(load_images
)、保存取消映射的处理函数指针(unmap_images
)。
Map images
这里map_images
会调用map_images_nolock
,这个函数主要会做两件事:
Selector initialization
一是遍历当前image
中所有的class
并计算出使用到的所有libobjc
库中的sel
的数量,然后调用sel_init
对所有sel
进行一次初始化selector table
注册 (Initialize selector tables and register selectors used internally.
);
Read images
二是读入该镜像文件(_read_images
),这个函数会处理当前镜像文件的头部信息。源码注释如下:
Perform initial processing of the headers
in the linked list beginning with headerList.
该过程又包含若干步骤:
- 获取镜像文件中的类列表,遍历列表进行类读取(调用
readClass
); - 遍历注册所有的
selector
名字(调用__sel_registerName
); - 遍历读取协议列表(调用
_getObjc2ProtocolList
,readProtocol
); - 遍历实例化运行时类结构(调用
realizeClass
)。
以上是Read images
的主要步骤,省略了一些打印和悬垂指针的判断条件处理等等。下面具体分析各个主要函数做的事情:
Read class
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be:
* - cls
* - nil (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
源码函数说明注释如上,该函数会初始化类结构体的class_rw_t
类型的data (rw)
并强制转换成该结构体的class_ro_t
类型的数据ro
;同时把rw
赋给本类。也就是说初始化读取时,本类的ro
和rw
是相同的。核心代码如下:
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro;
memcpy(newCls, cls, sizeof(objc_class));
rw->ro = (class_ro_t *)newCls->data();
newCls->setData(rw);
Realize class
* realizeClass
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Returns the real class structure for the class.
函数说明注释中解释得很清楚,这个函数主要是用于执行类的第一次初始化,其中包括了给rw
数据分配内存空间,同时构造这个类的结构体。
从函数内部源码看到,具体的执行步骤有:
- 初始化类结构体的基本变量,如
ro
(基本只读数据),rw
(可读写数据),supercls
(超类),metacls
(元类),isMeta
(元类标志位)等;
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
- 给
ro
,rw
分配内存地址;
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
}
isMeta = ro->flags & RO_META;
- 确定
rw
的版本;
rw->version = isMeta ? 7 : 0; // old runtime went up to 6
- 对超类和元类分别进行
realize class
;
// Realize superclass and metaclass, if they aren't already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
supercls = realizeClass(remapClass(cls->superclass));
metacls = realizeClass(remapClass(cls->ISA()));
- 初始化本类的isa并关联超类;
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
- 继承超类的原始变量并初始化本类的变量内存布局;
//static void reconcileInstanceVariables
//(Class cls, Class supercls, const class_ro_t*& ro)
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
这里reconcileInstanceVariables
发挥的作用就是确定本类的内存布局。
具体来说,继承于NSObject
的类在不同版本的iOS
下应该是不会因为系统增加了基类变量而导致子类的内存布局出问题的。
比如说1.0的系统NSObject
中有n
个变量,他们的内存布局是从0-n
的一个范围;子类如果继承于NSObject
并增加了m
个自定义的变量,理论上这m
个变量应该是追加在0-n
的末尾,变成0-(n+m)
。
如果下个版本的NSObject
增加了x
个变量,基类的内存布局变成了0-(n+x)
,如果runtime
不进行相应的布局调整,那么子类自定义的m
个变量可能就会覆盖掉系统新增的那x
个变量,造成内存覆盖的问题。
所以这个函数的作用,就是根据基类的变量内存布局,进行相应的调整。内部会调用moveIvars
来进行变量在内存上移动。
- 关联超类的继承链;
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
- 关联类别列表。
// Attach categories
methodizeClass(cls);
Methodize class
进一步阅读这个函数,源码注释如下:
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
该函数会对属性列表、方法列表和协议列表做一些排序上的调整;然后会把外部的类别整合进来,关键代码如下:
Attach categories
// Attach categories.
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
查看attachCategories
,有如下注释:
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
即该函数会将类别中的方法列表,属性和协议列表分别都加入本类中,并假定了类别列表加载的顺序是根据类别文件的加载顺序。
注意其中加载方法列表的代码如下:
rw->methods.attachLists(mlists, mcount);
Attach category method lists
而attachLists
的关键实现如下:
array()->count = newCount;
memmove(array()->lists + addedCount,
array()->lists, oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists,
addedLists, addedCount * sizeof(array()->lists[0]));
可以看到本类在加载类别中的方法列表时会先使用memmove
将本类的原始方法所占内存向后移动类别方法所需的空间偏移,然后再用memcpy
将类别方法复制到本类的方法列表的头部空间位置。这一点的运行时表现就是当我们使用类别复写了类的一个已有方法时,优先会调用的是类别复写的方法而不是已有方法。此时如果想在类别存在同名方法的前提下依然调用原方法,则可以利用运行时获取方法列表,从列表尾部往回遍历,找到第一个SEL相同的方法指针并执行即可。
注意到,这个runtime
初始化过程不包含任何类的initialization
,而仅仅是做了运行时类结构体的一些准备工作。这是因为类的initialization
(_class_initialize
)会发生在对某个类或类实例第一次发送消息时完成,也就是在_class_lookupMethodAndLoadCache3 -> lookUpImpOrForward
中完成,同时会发送+initialize
消息给复写方法。这是消息发送流程,在此不具体说明。
至此,Objc runtime
初始化的一些关键步骤就分析完了,还有一些细节部分没有覆盖,有时间会继续进行完善。