Runtime加载过程
该文章属于<简书 — 刘小壮>原创,转载请注明:
<简书 — 刘小壮> https://www.jianshu.com/p/4fb2d7014e9e
博客配图
程序加载过程
在iOS程序中会用到很多系统的动态库,这些动态库都是动态加载的。所有iOS程序共用一套系统动态库,在程序开始运行时才会开始链接动态库。
the dynamic link editor除了在项目设置里显式出现的动态库外,还会有一些隐式存在的动态库。例如objc
和Runtime
所属的libobjc.dyld
和libSystem.dyld
,在libSystem
中包含常用的libdispatch(GCD)
、libsystem_c
(C语言基础库)、libsystem_blocks(Block)
等。
使用动态库的优点:
- 防止重复。iOS系统中所有
App
公用一套系统动态库,防止重复的内存占用。 - 减少包体积。因为系统动态库被内置到iOS系统中,所以打包时不需要把这部分代码打进去,可以减小包体积。
- 动态性。因为系统动态库是动态加载的,所以可以在更新系统后,将动态库换成新的动态库。
加载过程
在应用程序启动后,由dyld(the dynamic link editor)
进行程序的初始化操作。大概流程就像下面列出的步骤,其中第3、4、5步会执行多次,在ImageLoader
加载新的image
进内存后就会执行一次。
- 在引用程序启动后,由
dyld
将应用程序加载到二进制中,并完成一些文件的初始化操作。 -
Runtime
向dyld
中注册回调函数。 - 通过
ImageLoader
将所有image
加载到内存中。 -
dyld
在image
发生改变时,主动调用回调函数。 -
Runtime
接收到dyld
的函数回调,开始执行map_images
、load_images
等操作,并回调+load
方法。 - 调用
main()
函数,开始执行业务代码。
ImageLoader
是image
的加载器,image
可以理解为编译后的二进制。
下面是在Runtime
的map_images
函数打断点,观察回调情况的汇编代码。可以看出,调用是由dyld
发起的,由ImageLoader
通知dyld
进行调用。
关于dyld
我并没有深入研究,有兴趣的同学可以到Github上下载源码研究一下。
动态加载
一个OC程序可以在运行过程中动态加载和链接新类或Category
,新类或Category
会加载到程序中,其处理方式和其他类是相同的。动态加载还可以做许多不同的事,动态加载允许应用程序进行自定义处理。
OC提供了objc_loadModules
运行时函数,执行Mach-O
中模块的动态加载,在上层NSBundle
对象提供了更简单的访问API
。
map images
在Runtime
加载时,会调用_objc_init
函数,并在内部注册三个函数指针。其中map_images
函数是初始化的关键,内部完成了大量Runtime
环境的初始化操作。
在map_images
函数中,内部也是做了一个调用中转。然后调用到map_images_nolock
函数,内部核心就是_read_images
函数。
void _objc_init(void)
{
// .... 各种init
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
rwlock_writer_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
在_read_images
函数中完成了大量的初始化操作,函数内部代码量比较大,下面是精简版带注释的源代码。
先整体梳理一遍_read_images
函数内部的逻辑:
- 加载所有类到类的
gdb_objc_realized_classes
表中。 - 对所有类做重映射。
- 将所有
SEL
都注册到namedSelectors
表中。 - 修复函数指针遗留。
- 将所有
Protocol
都添加到protocol_map
表中。 - 对所有
Protocol
做重映射。 - 初始化所有非懒加载的类,进行
rw
、ro
等操作。 - 遍历已标记的懒加载的类,并做初始化操作。
- 处理所有
Category
,包括Class
和Meta Class
。 - 初始化所有未初始化的类。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
static bool doneOnce;
TimeLogger ts(PrintImageTimes);
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
if (!doneOnce) {
doneOnce = YES;
// 实例化存储类的哈希表,并且根据当前类数量做动态扩容
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
}
// 由编译器读取类列表,并将所有类添加到类的哈希表中,并且标记懒加载的类并初始化内存空间
for (EACH_HEADER) {
if (! mustReadClasses(hi)) {
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
/** 将新类添加到哈希表中 */
// 从编译后的类列表中取出所有类,获取到的是一个classref_t类型的指针
classref_t *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
// 数组中会取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系统类,例如CF、Fundation、libdispatch中的类。以及自己创建的类
Class cls = (Class)classlist[i];
// 通过readClass函数获取处理后的新类,内部主要操作ro和rw结构体
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
// 初始化所有懒加载的类需要的内存空间
if (newCls != cls && newCls) {
// 将懒加载的类添加到数组中
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
// 将未映射Class和Super Class重映射,被remap的类都是非懒加载的类
if (!noClassesRemapped()) {
for (EACH_HEADER) {
// 重映射Class,注意是从_getObjc2ClassRefs函数中取出类的引用
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
// 重映射父类
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
}
}
// 将所有SEL都注册到哈希表中,是另外一张哈希表
static size_t UnfixedSelectors;
sel_lock();
for (EACH_HEADER) {
if (hi->isPreoptimized()) continue;
bool isBundle = hi->isBundle();
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
// 注册SEL的操作
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
// 修复旧的函数指针调用遗留
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
for (i = 0; i < count; i++) {
// 内部将常用的alloc、objc_msgSend等函数指针进行注册,并fix为新的函数指针
fixupMessageRef(refs+i);
}
}
// 遍历所有协议列表,并且将协议列表加载到Protocol的哈希表中
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
// cls = Protocol类,所有协议和对象的结构体都类似,isa都对应Protocol类
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
// 获取protocol哈希表
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->isPreoptimized();
bool isBundle = hi->isBundle();
// 从编译器中读取并初始化Protocol
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
// 修复协议列表引用,优化后的images可能是正确的,但是并不确定
for (EACH_HEADER) {
// 需要注意到是,下面的函数是_getObjc2ProtocolRefs,和上面的_getObjc2ProtocolList不一样
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}
// 实现非懒加载的类,对于load方法和静态实例变量
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// 实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
realizeClass(cls);
}
}
// 遍历resolvedFutureClasses数组,实现所有懒加载的类
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
// 实现懒加载的类
realizeClass(resolvedFutureClasses[i]);
resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
}
free(resolvedFutureClasses);
}
// 发现和处理所有Category
for (EACH_HEADER) {
// 外部循环遍历找到当前类,查找类对应的Category数组
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
// 内部循环遍历当前类的所有Category
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
// 首先,通过其所属的类注册Category。如果这个类已经被实现,则重新构造类的方法列表。
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// 将Category添加到对应Class的value中,value是Class对应的所有category数组
addUnattachedCategoryForClass(cat, cls, hi);
// 将Category的method、protocol、property添加到Class
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
}
// 这块和上面逻辑一样,区别在于这块是对Meta Class做操作,而上面则是对Class做操作
// 根据下面的逻辑,从代码的角度来说,是可以对原类添加Category的
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
}
// 初始化从磁盘中加载的所有类,发现Category必须是最后执行的
// 从runtime objc4-532版本源码来看,DebugNonFragileIvars字段一直是-1,所以不会进入这个方法中
if (DebugNonFragileIvars) {
realizeAllClasses();
}
#undef EACH_HEADER
}
其内部还调用了很多其他函数,后面会详细介绍函数内部实现。
load images
在项目中经常用到load
类方法,load
类方法的调用时机比main
函数还要靠前。load
方法是由系统来调用的,并且在整个程序运行期间,只会调用一次,所以可以在load
方法中执行一些只执行一次的操作。
一般Method Swizzling
都会放在load
方法中执行,这样在执行main
函数前,就可以对类方法进行交换。可以确保正式执行代码时,方法肯定是被交换过的。
如果对一个类添加Category后,并且重写其原有方法,这样会导致Category中的方法覆盖原类的方法。但是load方法却是例外,所有Category和原类的load方法都会被执行。
源码分析
load
方法由Runtime
进行调用,下面我们分析一下load
方法的实现,load
的实现源码都在objc-loadmethod.mm
中。
在一个新的工程中,我们创建一个TestObject
类,并在其load
方法中打一个断点,看一下系统的调用堆栈。
从调用栈可以看出,是通过系统的动态链接器dyld
开始的调用,然后调到Runtime
的load_images
函数中。load_images
函数是通过_dyld_objc_notify_register
函数,将自己的函数指针注册给dyld
的。
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);
}
在load_images
函数中主要做了两件事,首先通过prepare_load_methods
函数准备Class load list
和Category load list
,然后通过call_load_methods
函数调用已经准备好的两个方法列表。
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!hasLoadMethods((const headerType *)mh)) return;
prepare_load_methods((const headerType *)mh);
call_load_methods();
}
首先我们看一下prepare_load_methods
函数的实现,看一下其内部是怎么查找load
方法的。可以看到,其内部主要分为两部分,查找Class
的load
方法列表和查找Category
方法列表。
准备类的方法列表时,首先通过_getObjc2NonlazyClassList
函数获取所有非懒加载类的列表,这时候获取到的是一个classref_t
类型的数组,然后遍历数组添加load
方法列表。
Category
过程也是类似,通过_getObjc2NonlazyCategoryList
函数获取所有非懒加载Category
的列表,得到一个category_t
类型的数组,需要注意的是这是一个指向指针的指针。然后对其进行遍历并添加到load
方法列表,Class
和Category
的load
方法列表是两个列表。
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
// 获取到非懒加载的类的列表
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 设置Class的调用列表
schedule_class_load(remapClass(classlist[i]));
}
// 获取到非懒加载的Category列表
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
// 忽略弱链接的类别
if (!cls) continue;
// 实例化所属的类
realizeClass(cls);
// 设置Category的调用列表
add_category_to_loadable_list(cat);
}
}
在添加类的load
方法列表时,内部会递归遍历把所有父类的load
方法都添加进去,顺着继承者链的顺序添加,会先把父类添加在前面。然后会调用add_class_to_loadable_list
函数,将自己的load
方法添加到对应的数组中。
static void schedule_class_load(Class cls)
{
if (!cls) return;
// 已经添加Class的load方法到调用列表中
if (cls->data()->flags & RW_LOADED) return;
// 确保super已经被添加到load列表中,默认是整个继承者链的顺序
schedule_class_load(cls->superclass);
// 将IMP和Class添加到调用列表
add_class_to_loadable_list(cls);
// 设置Class的flags,表示已经添加Class到调用列表中
cls->setInfo(RW_LOADED);
}
而Category
则不需要考虑父类的问题,所以直接在prepare_load_methods
函数中遍历Category
数组,然后调用add_category_to_loadable_list
函数即可。
在add_category_to_loadable_list
函数中,会判断当前Category
有没有实现load
方法,如果没有则直接return
,如果实现了则添加到loadable_categories
数组中。
类的add_class_to_loadable_list
函数内部实现也是类似,区别在于类的数组叫做loadable_classes
。
void add_category_to_loadable_list(Category cat)
{
IMP method;
// 获取Category的load方法的IMP
method = _category_getLoadMethod(cat);
// 如果Category没有load方法则return
if (!method) return;
// 如果已使用大小等于数组大小,对数组进行动态扩容
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
到此为止,loadable_classes
和loadable_categories
两个数组已经准备好了,load_images
会调用call_load_methods
函数执行这些load
方法。在这个方法中,call_class_loads
函数是负责调用类方法列表的,call_category_loads
负责调用Category
的方法列表。
void call_load_methods(void)
{
bool more_categories;
void *pool = objc_autoreleasePoolPush();
do {
// 反复执行call_class_loads函数,直到数组中没有可执行的load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
more_categories = call_category_loads();
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
下面是调用类方法列表的代码,内部主要是通过对loadable_classes
数组进行遍历,并获取到loadable_class
的结构体,结构体中存在Class
和IMP
,然后直接调用即可。
Category
的调用方式和类的一样,就不在下面贴代码了。需要注意的是,load
方法都是直接调用的,并没有走运行时的objc_msgSend
函数。
static void call_class_loads(void)
{
int i;
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
(*load_method)(cls, SEL_load);
}
if (classes) free(classes);
}
struct loadable_class {
Class cls; // may be nil
IMP method;
};
根据上面的源码分析,我们可以看出load
方法的调用顺序应该是“父类 -> 子类 -> 分类”的顺序。因为执行加载Class
的时机是在Category
之前的,而且load
子类之前会先load
父类,所以是这种顺序。
initialize
和load
方法类似的也有initialize
方法,initialize
方法也是由Runtime
进行调用的,自己不可以直接调用。与load
方法不同的是,initialize
方法是在第一次调用类所属的方法时,才会调用initialize
方法,而load
方法是在main
函数之前就全部调用了。所以理论上来说initialize
可能永远都不会执行,如果当前类的方法永远不被调用的话。
下面我们研究一下
initialize
在Runtime
中的源码。
在向对象发送消息时,lookUpImpOrForward
函数中会判断当前类是否被初始化,如果没有被初始化,则先进行初始化再调用类的方法。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver);
// ....省略好多代码
// 第一次调用当前类的话,执行initialize的代码
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
}
// ....省略好多代码
在进行初始化的时候,和load
方法的调用顺序一样,会按照继承者链先初始化父类。_class_initialize
函数中关键的两行代码是callInitialize
和lockAndFinishInitializing
的调用。
// 第一次调用类的方法,初始化类对象
void _class_initialize(Class cls)
{
Class supercls;
bool reallyInitialize = NO;
// 递归初始化父类。initizlize不用显式的调用super,因为runtime已经在内部调用了
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
if (reallyInitialize) {
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
performForkChildInitialize(cls, supercls);
return;
}
@try {
// 通过objc_msgSend()函数调用initialize方法
callInitialize(cls);
}
@catch (...) {
@throw;
}
@finally {
// 执行initialize方法后,进行系统的initialize过程
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
}
通过objc_msgSend
函数调用initialize
方法。
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
lockAndFinishInitializing
函数中会完成一些初始化操作,其内部会调用_finishInitializing
函数,在函数内部会调用class
的setInitialized
函数,核心工作都由setInitialized
函数完成。
static void lockAndFinishInitializing(Class cls, Class supercls)
{
monitor_locker_t lock(classInitLock);
if (!supercls || supercls->isInitialized()) {
_finishInitializing(cls, supercls);
} else {
_finishInitializingAfter(cls, supercls);
}
}
负责初始化类和元类,函数内部主要是查找当前类和元类中是否定义了某些方法,然后根据查找结果设置类和元类的一些标志位。
void
objc_class::setInitialized()
{
Class metacls;
Class cls;
// 获取类和元类对象
cls = (Class)this;
metacls = cls->ISA();
bool inherited;
bool metaCustomAWZ = NO;
if (MetaclassNSObjectAWZSwizzled) {
metaCustomAWZ = YES;
inherited = NO;
}
else if (metacls == classNSObject()->ISA()) {
// 查找是否实现了alloc和allocWithZone方法
auto& methods = metacls->data()->methods;
for (auto mlists = methods.beginCategoryMethodLists(),
end = methods.endCategoryMethodLists(metacls);
mlists != end;
++mlists)
{
if (methodListImplementsAWZ(*mlists)) {
metaCustomAWZ = YES;
inherited = NO;
break;
}
}
}
else if (metacls->superclass->hasCustomAWZ()) {
metaCustomAWZ = YES;
inherited = YES;
}
else {
auto& methods = metacls->data()->methods;
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
if (methodListImplementsAWZ(*mlists)) {
metaCustomAWZ = YES;
inherited = NO;
break;
}
}
}
if (!metaCustomAWZ) metacls->setHasDefaultAWZ();
if (PrintCustomAWZ && metaCustomAWZ) metacls->printCustomAWZ(inherited);
bool clsCustomRR = NO;
if (ClassNSObjectRRSwizzled) {
clsCustomRR = YES;
inherited = NO;
}
// 查找元类是否实现MRC方法,如果是则进入if语句中
if (cls == classNSObject()) {
auto& methods = cls->data()->methods;
for (auto mlists = methods.beginCategoryMethodLists(),
end = methods.endCategoryMethodLists(cls);
mlists != end;
++mlists)
{
if (methodListImplementsRR(*mlists)) {
clsCustomRR = YES;
inherited = NO;
break;
}
}
}
else if (!cls->superclass) {
clsCustomRR = YES;
inherited = NO;
}
else if (cls->superclass->hasCustomRR()) {
clsCustomRR = YES;
inherited = YES;
}
else {
// 查找类是否实现MRC方法,如果是则进入if语句中
auto& methods = cls->data()->methods;
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
if (methodListImplementsRR(*mlists)) {
clsCustomRR = YES;
inherited = NO;
break;
}
}
}
if (!clsCustomRR) cls->setHasDefaultRR();
if (PrintCustomRR && clsCustomRR) cls->printCustomRR(inherited);
metacls->changeInfo(RW_INITIALIZED, RW_INITIALIZING);
}
需要注意的是,initialize
方法和load
方法不太一样,Category
中定义的initialize
方法会覆盖原方法而不是像load
方法一样都可以调用。
简书由于排版的问题,阅读体验并不好,布局、图片显示、代码等很多问题。所以建议到我Github
上,下载Runtime PDF
合集。把所有Runtime
文章总计九篇,都写在这个PDF
中,而且左侧有目录,方便阅读。
下载地址:Runtime PDF
麻烦各位大佬点个赞,谢谢!😁