前言
我们上一篇章应用程序的加载原理中还遗留了一些问题,这些问题跟我们后面讲的类的加载原理还是有些关系的,所以这里首先我们把这个问题分析下。
map_images & load_images的调用
我们在应用程序的加载原理中讲到,_dyld_objc_notify_register(&map_images, load_images, unmap_image) ——> registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped),我们看下图:
我们看map_images和load_images的调用,其实就是这里sNotifyObjcMapped和sNotifyObjcInit的调用。我们在dyld源码中全局搜索下sNotifyObjcMapped,找到它被调用的地方,最终找到如下图:
我们看到sNotifyObjcMapped是在notifyBatchPartial()方法中被调用的,我们接着找notifyBatchPartial被调用地方:
我们看到它在registerObjcNotifiers被调用,也就是sNotifyObjcMapped被调用的流程是这样的:_objc_init ——> _dyld_objc_notify_register ——> registerObjCNotifiers ——> notifyBatchPartial ——> sNotifyObjcMapped(map_images)这样的调用顺序。我们接着全局搜索下sNotifyObjcInit,看它是在哪里被调用的:
我们从上图可以很清晰看到,sNotifyObjcInit(load_images)在notifySingle中调用,而notifySingle在recursiveInitialization中被调用,所以sNotifyObjcInit调用流程是这样:recursiveInitialization ——> notifySingle ——> sNotifyObjcInit;而如果你看过我之前的应用程序加载原理可以知道,_dyld_objc_notify_register是沟通dyld和objc的一个重要函数。因为在objc源码中可以看到该函数被_objc_init调用:
而_objc_init的调用流程是这样的:recursiveInitialization(dyld) ——> doInitialization(dyld) ——> doModInitFunctions(dyld) ——>libSystem_initializer(libSystem.dylib) ——> libdispatch_init ——> _os_object_init ——> _objc_init ——> _dyld_objc_notify_register;我们在recursiveInitialization方法中可以看到,notifySingle的调用是在doInitialization(初始化)之前的,两者都是在recursiveInitialization中被调用的。这里就有点奇怪了,我们的sNotifyObjcMapped(map_images)为啥会在doModInitFunctions(初始化)之前调用呢,其实并不冲突,因为recursiveInitialization这里是递归进行初始化的,第一次进来的时候可能什么文件也没有,所以并一定会调用sNotifyObjcMapped。我们看到recursiveInitialization方法中,有两次调用notifySingle的地方,若初始进来,第一个notifySingle没有调用,那么相当于首先调用doModInitFunctions初始化map_images,接着执行map_images(),之后调用context.notifySingle()进行load_images的调用。
load_images方法解析
load_images方法里具体做了什么呢,我们看下它的相关实现:
我们可以看到两个关键的方法prepare_load_methods((const headerType *)mh)和call_load_methods(),我们先看第一个方法prepare_load_methods:
我们先进入schedule_class_load()方法看下它的实现:
可以看到它是一个递归调用吗,找完当前类,会接着添加它的父类,直到cls为nil。我们接着进入add_class_to_loadable_list(cls):
我们可以非常清楚的看到,这里是获取当前的类的load方法,然后存入
loadable_classes中。我们可看下getLoadMethod()方法:
分类跟主类是同样的存储方法,我们看下add_category_to_loadable_list:
我们可以看到同样是_category_getLoadMethod(cat)获取分类load方法,然后存入loadable_categories中,只不过跟类存的不在一个地方。我们现在看下call_load_methods()方法:
我们其实可以非常清楚地看到,这里就是调用类里和分类里找到的那些load方法,我们可以进去看下call_class_loads():
所以到这里我们基本可以明白,load_images里主要就是查找类及分离里的load方法,并调用它。
cxx(c++)与load方法的调用顺序解析
我们在main.m文件中声明一个c++方法,CTPerson实现下load方法,如下图:
我们运行下工程,打印输出看下结果:
这里我们看到cxx函数在doInitialization调用的时候就被调用了,但是load方法是在notifySingle里调用的,为啥load打印输出在cxx之前呢。这里我们在objc源码中再定义一个c++方法,运行调试下结果就清楚了:
调试打印结果:
第一个打印输出的cxx是存在于objc源码中,说明首先调用doInitialization初始化的镜像文件,属于镜像文件的内部初始化,并不属于当前工程的初始化,然后执行的load(context.notifySingle)方法,接着才执行的工程中的cxx方法。
objc_init分析
我们上面讲到_objc_init ——> _dyld_objc_notify_register,这两个方法是重点,我们先看下_objc_init方法:
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();
//关于线程key的绑定 - 比如每线程数据的析构函数
tls_init();
//运行C++静态构造函数,在dyld调用我们的静态函数之前,’libc‘会调用我们的objc_init()
static_init();
//runtime 运行时环境初始化
runtime_init();
//初始化libobjc的异常处理系统
exception_init();
#if __OBJC2__
//缓存条件初始化
cache_t::init();
#endif
//启动回调机制
_imp_implementationWithBlock_init();
//&map_images --- 指针传递,保持同步变化
//load_images--- 主要是load方法的调用
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
map_images解析
我们看下map_images方法实现,我们这里主要看镜像文件的加载过程,所以就寻找镜像文件相关的代码:
我们从map_images方法中找到map_images_nolock,然后在map_images_nolock看到一句重要的代码_read_images,我们重点看下这里。
_read_images解析
这里_read_images是如何读取镜像文件呢,我们看下该方法的实现,这里代码很长,我们先从整体看下它的大致流程:
这里我们从它的相关log输出看到一些大概的流程,错误类处理、协议读取、分类处理等相关处理,我们重点关注类的相关读取和加载处理。我们可以看到一个重点方法readClass:
进入readClass方法后,这时候我们调试下,因为代码判断有很多,我们可以打印出我们自定义的类进行调试观察:
经过工程调试验证,代码最终没有进入rw、ro读取那里,只是调用了addNamedClass(cls,mangledName,replacing)和addClassTableEntry(cls):
我们可以看下addNamedClass,这里是把类名等相关信息存入一个全局表中:
这个NXMapTable是在read_images条件控制一次加载的过程创建的,我们可以看到相关代码:
这里创建了一个全局表,用于存储一些类的相关信息,而我们在objc_init那里看到runtime_init有创建了一张表,它用于存储开辟过内存的类的相关存储:
代码注释中也有明确指出:
这里readClass后面调用的addClassTableEntry就是插入的这张表allocatedClasses:
分析到这里,其实我们还是有点奇怪的,因为到目前为止,类的rw、ro还没有进行处理过,它是在哪里处理的呢,我们后面进行讲解。