在我们的日常开发中我们都知道,应用程序的执行都会依赖一些底层的基础库,例如 UIKit
, Foundation
等。那么这些库是怎么加载到内存中的呢?这里就要讲到 dyld
动态链接器。这里也给大家推荐一本书籍 “程序员的自我修养”。这本书主要介绍系统软件的运行机制和原理,涉及在Windows和Linux两个系统平台上,一个应用程序在编译、链接和运行时刻所发生的各种事项。
库的概念:可执行的二进制文件,能被加载到内存中。库分为两种形式,静态库(.a , .lib 等)和动态库(.so , .dll , .dylib 等)。
静态库:链接时会被完整的复制到可执行文件中,所以如果两个程序都用了某个静态库,那么每个二进制可执行文件里面其实都含有这份静态库的代码。
动态库:链接时不复制,在程序启动后用动态加载,然后再决议符号,所以理论上动态库只用存在一份,好多个程序都可以动态链接到这个动态库上面,达到了节省内存(不是磁盘是内存中只有一份动态库),还有另外一个好处,由于动态库并不绑定到可执行程序上,所以我们想升级这个动态库就很容易,windows和linux上面一般插件和模块机制都是这样实现的。
dyld的引出
main
函数作为我们程序的入口,那么程序的加载流程一定发生在 main
函数之前,这里我们对 main
函数打断点,来看看在这之前都调用了哪些方法。
在这里我们可以看到,main
函数之前执行了 start
方法,那么我们对 start
方法下个符号断点来看一下。
但是 start
方法并没有断到,说明真正调用的不是 start
方法。那么我们再来尝试下其他方法,我们都知道 load
方法在 main
函数之前执行,我们再来对 load
方法打断点看看。
通过 lldb
调试,bt
命令打印函数调用堆栈信息,我们可以看到,最开始调用的是 dyld
文件的 _dyld_start
方法。这里我们就来打开 dyld
的源码。这里是源码链接 dyld 源码,大家也可以自己下载。
dyld
(the dynamic link editor)是苹果的动态链接器,用来加载所有的库和可执行文件,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld
负责余下的工作。这里 WWDC2017 也有介绍到dyld
。
dyld执行流程
1. _dyld_start
第一步我们打开源码搜索 _dyld_start
,这里会对系统架构进行判断,这里会有注释,_dyld_start
方法之后会调用 c++
方法 dyldbootstrap::start
。
2. dyldbootstrap::start
因为 c++
有命名空间,所以我们先搜索 dyldbootstrap
,然后再在当前文件搜索 start
方法。该方法里面最主要的就是最后一句 return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue)
。
3. _main
这里我们可以看到,_main
函数下有将近一千行代码,那么我们应该怎么看重点代码呢?首先我们要明白 dyld
最主要的功能就是库的加载,所以我们只看跟加载相关的代码,其他的逻辑代码我们就忽略不看。还有就是当前函数最后返回的是 return result
,所以我们用倒推法的形式从最后一行开始往上看。最后的执行步骤及处理的事情总结如下。
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
//主程序信息相关的处理
getHostInfo(mainExecutableMH, mainExecutableSlide);
// 确定当前是否有共享缓存,共享缓存由系统级进行处理的
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
// 继续往上查找找到 sMainExecutable 赋值的地方
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
// link 主程序
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
// 往上查找找到 result 赋值的地方,找到 sMainExecutable,接着查看 sMainExecutable
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
// 因为最后返回的是 result,所以 result 是很重要的信息,所以我们从这里开始看,找跟 result 相关的代码,找 result 赋值的地方
return result;
}
1. 条件准备:环境, 平台, 版本, 路径, 主机信息
在 _main
函数开始到 checkSharedRegionDisable
函数之前都是在做加载前的一些准备工作,判断系统环境,平台架构版本等信息。对我们探究加载流程,这些都不是重点。
2. checkSharedRegionDisable
确定当前是否有共享缓存,共享缓存由系统级进行处理的。
3. instantiateFromLoadedImage
实例化主程序
// 实例化主程序 image:可执行文件(dyld第一个加载的image是主程序)
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
// try mach-o loader
// if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
addImage(image);
return (ImageLoaderMachO*)image;
// }
// throw "main executable not a known format";
}
4. 加载插入的动态库
loadInsertedDylib(*lib)
5. link 主程序
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1)
6. link 插入的动态库
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
image->setNeverUnloadRecursive();
}
7. 弱引用绑定主程序
sMainExecutable->weakBind(gLinkContext);
这里是在所有的镜像文件绑定完毕才弱引用绑定。
8. 运行所有初始化程序
void initializeMainExecutable()
{
// record that we've reached this step
gLinkContext.startedInitializingMainExecutable = true;
// 这里是拿到所有镜像文件的 count,然后循环运行每个镜像文件
ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
if ( rootCount > 1 ) {
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// 运行主程序
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
if ( gLibSystemHelpers != NULL )
(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
// dump info if requested
if ( sEnv.DYLD_PRINT_STATISTICS )
ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
runInitializers
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
uint64_t t1 = mach_absolute_time();
mach_port_t thisThread = mach_thread_self();
ImageLoader::UninitedUpwards up;
up.count = 1;
up.imagesAndPaths[0] = { this, this->getPath() };
processInitializers(context, thisThread, timingInfo, up);
context.notifyBatch(dyld_image_state_initialized, false);
mach_port_deallocate(mach_task_self(), thisThread);
uint64_t t2 = mach_absolute_time();
fgTotalInitTime += (t2 - t1);
}
这里的内容比较多,会放到下面单独去讲。
9. 通知 dyld
此进程将要进入 main()
notifyMonitoringDyldMain
runInitializers
因为上面 dyld
执行流程第 8 步中的 runInitializers
方法比较重要,内容也比较多,这里单独拿出来讲。
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
uint64_t t1 = mach_absolute_time();
mach_port_t thisThread = mach_thread_self();
ImageLoader::UninitedUpwards up;
up.count = 1;
up.imagesAndPaths[0] = { this, this->getPath() };
processInitializers(context, thisThread, timingInfo, up);
context.notifyBatch(dyld_image_state_initialized, false);
mach_port_deallocate(mach_task_self(), thisThread);
uint64_t t2 = mach_absolute_time();
fgTotalInitTime += (t2 - t1);
}
1. processInitializers
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
uint32_t maxImageCount = context.imageCount()+2;
ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
ImageLoader::UninitedUpwards& ups = upsBuffer[0];
ups.count = 0;
// 这里这个循环是重点,在当前线程镜像文件开始加载
for (uintptr_t i=0; i < images.count; ++i) {
images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
}
// If any upward dependencies remain, init them.
if ( ups.count > 0 )
processInitializers(context, thisThread, timingInfo, ups);
}
1. recursiveInitialization
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
recursive_lock lock_info(this_thread);
recursiveSpinLock(lock_info);
if ( fState < dyld_image_state_dependents_initialized-1 ) {
uint8_t oldState = fState;
// break cycles
fState = dyld_image_state_dependents_initialized-1;
try {
// initialize lower level libraries first
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
// don't try to initialize stuff "above" me yet
if ( libIsUpward(i) ) {
// 镜像文件拿到路径
uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
uninitUps.count++;
}
else if ( dependentImage->fDepth >= fDepth ) {
// 依赖文件递归初始化
dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
}
}
// record termination order
if ( this->needsTermination() )
context.terminationRecorder(this);
// 这里先加载依赖文件,因为当前程序有时候会依赖下层文件,如果下层没有加载完,当前程序会跑不起来,所以要先加载依赖文件再加载本身
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
bool hasInitializers = this->doInitialization(context);
// 这里开始加载当前文件,这里标记一个状态然后到 notifySingle
fState = dyld_image_state_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_initialized, this, NULL);
}
1. notifySingle
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
uint64_t t0 = mach_absolute_time();
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
// 重点是这一行
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
uint64_t t1 = mach_absolute_time();
uint64_t t2 = mach_absolute_time();
uint64_t timeInObjC = t1-t0;
uint64_t emptyTime = (t2-t1)*100;
if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
timingInfo->addTime(image->getShortName(), timeInObjC);
}
}
}
1. sNotifyObjCInit
我们全局搜索 sNotifyObjCInit
,找到 sNotifyObjCInit
在 registerObjCNotifiers
函数中被赋值为第二个参数,所以我们搜索 registerObjCNotifiers
方法,看看第二个参数是什么。
通过查找看到 registerObjCNotifiers
方法在 _dyld_objc_notify_register
方法中被调用。这里是重点。
我们在 objc
源码中搜索 _dyld_objc_notify_register
可以看到是在 _objc_init
方法中被调用,第二个参数是 load_images
,也就是 sNotifyObjCInit
等于 load_images
。那么 _objc_init
是由哪里调用的呢?这里我们运行 objc
源码,通过断点,打印调用堆栈信息来看一下。
通过函数调用堆栈信息我们可以看到在 _objc_init
方法之前调用了 libdispatch.dylib
库的 _os_object_init
方法。 这里我们要借助 libdispatch
的源码来看一下。
在这里可以看到 _objc_init
在 _os_object_init
中被调用。
搜索 _os_object_init
可以发现该方法是在 libdispatch_init
函数中被调用。
接着我们可以看到在 libdispatch_init
函数之前调用了 libSystem
库的 libSystem_initializer
方法。再打开 Libsystem
的源码,看看 libdispatch_init
函数在哪里被调用。
通过搜索可以看到是在 libSystem_initializer
中被调用。
接着我们看堆栈信息可以看到 libSystem_initializer
方法是由 dyld
的 doModInitFunctions
方法调起。接着我们再回到 dyld
源码。
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
if ( fHasInitializers ) {
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if ( type == S_MOD_INIT_FUNC_POINTERS ) {
for (size_t j=0; j < count; ++j) {
// 这里 func 等于 libSystem_initializer
Initializer func = inits[j];
// verify initializers are in image
if ( ! this->containsAddress(stripPointer((void*)func)) ) {
dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
}
if ( ! dyld::gProcessInfo->libSystemInitialized ) {
// libSystem initializer must run first
const char* installPath = getInstallPath();
// 这里会有一个判断,在所有库加载之前先加载 libSystem,优先级最高
if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}
if ( context.verboseInit )
dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL);
{
dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
// 这里相当于执行 libSystem_initializer 函数
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
}
bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL);
if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
// now safe to use malloc() and other calls in libSystem.dylib
dyld::gProcessInfo->libSystemInitialized = true;
}
}
}
}
}
}
}
}
这里只保留了主要代码,这里主要是循环调用 libSystem
的 libSystem_initializer
方法。并且判断在所有库加载之前先加载 libSystem
,优先级最高。最后调用 libSystem_initializer
方法。
接着我们搜索 doModInitFunctions
方法, 发现是在 doInitialization
方法中调用了 doModInitFunctions
方法,跟我们堆栈的打印顺序也一致。
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
CRSetCrashLogMessage2(this->getPath());
// mach-o has -init and static initializers
doImageInit(context);
doModInitFunctions(context);
CRSetCrashLogMessage2(NULL);
return (fHasDashInit || fHasInitializers);
}
我们在前面 recursiveInitialization
方法中也有讲到在此方法中会调用 doInitialization
方法。
_objc_init
调用流程:doInitialization(dyld)
->doModInitFunctions(dyld)
->libSystem.B.dylib.libSystem_initializer:
->libdispatch.dylib.libdispatch_init:
->_os_object_init(libdispatch.dylib)
->_objc_init(libobjc)
1. notifySingle
与 doInitialization
的关联
我们在上面已经讲过了 notifySingle
其实就是间接调用 _dyld_objc_notify_register
函数,并且 _dyld_objc_notify_register
函数调用了 registerObjCNotifiers
函数,并对 sNotifyObjCMapped
、sNotifyObjCInit
、sNotifyObjCUnmapped
三个参数进行初始化赋值。但是这三个函数能否调用起来这里还不知道。
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
}
前面也讲到过 _objc_init
函数中会调用 _dyld_objc_notify_register
方法。
- map_images
map_images
-> map_images_nolock
-> _read_images
这里要讲到一个重要的方法 _read_images
, map_images
最终会调用 _read_images
方法。该方法主要是做了 sel
的读取,class
的读取,protocol
的读取及 bits
的处理等操作。那么 map_images
什么时候执行呢?也就是 sNotifyObjCMapped
什么时候执行。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
// sel 的读取
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) 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 sel = sel_registerNameNoLock(name, isBundle);
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
}
// class 的读取
for (EACH_HEADER) {
if (! mustReadClasses(hi, hasDyldRoots)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
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;
}
}
}
// protocol 的读取
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
ASSERT(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->hasPreoptimizedProtocols();
// Skip reading protocols if this is an image from the shared cache
// and we support roots
// Note, after launch we do need to walk the protocol as the protocol
// in the shared cache is marked with isCanonical() and that may not
// be true if some non-shared cache binary was chosen as the canonical
// definition
if (launchTime && isPreoptimized) {
if (PrintProtocols) {
_objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
hi->fname());
}
continue;
}
bool isBundle = hi->isBundle();
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
-
sNotifyObjCMapped
调用时机
在 dyld
源码中搜索 sNotifyObjCMapped
方法,可以看到该方法是在 notifyBatchPartial
函数中调用的。
接着搜索 notifyBatchPartial
方法,发现 notifyBatchPartial
函数在 registerObjCNotifiers
函数中有被调用。也就是 sNotifyObjCMapped
被赋值完成后,紧接着就调用了 notifyBatchPartial
函数,notifyBatchPartial
函数又调用了 sNotifyObjCMapped
函数。
-
sNotifyObjCInit
调用时机,也就是load_images
的调用时机
同样搜索 sNotifyObjCInit
方法,发现是在 notifySingle
中有调用。
接着我们再搜索 notifySingle
函数,在 recursiveInitialization
函数中可以看到在 doInitialization
之前调用了 notifySingle
函数。但是这里大家可能会有个疑问,明明是在 doInitialization
函数开始初始化的,也就是在这里才开始对 sNotifyObjCInit
进行赋值的,那么为什么在这之前调用了 notifySingle
函数呢?这是因为这里整体是递归调用,在第一次加载的时间,也就是加载 libSystem
的时候不需要依赖别的文件,所以先调用 doInitialization
函数,再调用 1662 行的 notifySingle
函数。所以这里 1654 行调用 notifySingle
函数,是因为上一个文件调用 doInitialization
函数之后初始化的 sNotifyObjCInit
函数。
-
load_images
实现细节
在开始的时候我们看到load
方法在main
函数之前执行,那么load
在哪里被调用的呢?这里我们来看一下load_images
的实现细节。
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// 调用所有的 load 方法
call_load_methods();
}
在 load_images
方法中调用了 prepare_load_methods
方法。
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
// 把所有懒加载的类准备好
for (i = 0; i < count; i++) {
// 添加主类的 load 方法
schedule_class_load(remapClass(classlist[i]));
}
// 把所有的分类准备好
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
// 把所有分类的 categorylist 方法准备好,并映射进来
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
// 添加分类的 load 方法
add_category_to_loadable_list(cat);
}
}
prepare_load_methods
方法主要做了类及分类的准备及分别添加主类及分类的 load
方法。这里我们先看一下主类 load
方法是如何添加的。
static void schedule_class_load(Class cls)
{
// 这里判断如果 cls 为空就返回
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// 该方法是递归调用,并把当前类的父类传参进来
schedule_class_load(cls->getSuperclass());
// 这里对类添加 load 方法
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
// 匹配 load 方法
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
// 匹配到 load 方法后,就对 loadable_classes 的 loadable_classes_used下标下的 cls 及 method 进行赋值
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
// 下标++
loadable_classes_used++;
}
IMP
objc_class::getLoadMethod()
{
runtimeLock.assertLocked();
const method_list_t *mlist;
ASSERT(isRealized());
ASSERT(ISA()->isRealized());
ASSERT(!isMetaClass());
ASSERT(ISA()->isMetaClass());
// 这里递归所有 baseMethods,匹配到 load 方法就返回 imp
mlist = ISA()->data()->ro()->baseMethods();
if (mlist) {
for (const auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
if (0 == strcmp(name, "load")) {
return meth.imp(false);
}
}
}
return nil;
}
这里我们来看一下分类的 load
方法是如何添加的。
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
// 匹配分类的 load 方法
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
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));
}
// 匹配到 load 方法只会,对分类的 loadable_categories 的 loadable_categories_used 下标下的 cat 及 method 进行赋值
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
// 下标++
loadable_categories_used++;
}
IMP
_category_getLoadMethod(Category cat)
{
runtimeLock.assertLocked();
const method_list_t *mlist;
// 遍历分类的 mlist,匹配到 load,返回 imp
mlist = cat->classMethods;
if (mlist) {
for (const auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
if (0 == strcmp(name, "load")) {
return meth.imp(false);
}
}
}
return nil;
}
在所有的准备完成之后就会调用 call_load_methods
方法。这里我们来看一下 call_load_methods
方法的实现细节。
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
// 首先最外层有个循环
do {
// 这里又有个循环,遍历调用 call_class_loads 方法
while (loadable_classes_used > 0) {
call_class_loads();
}
// 这里遍历调用分类的 load 方法
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
类的 load
方法调用:
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// 循环遍历数组,取出 load 对应的 method
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;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
// 调用 load 方法
(*load_method)(cls, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
分类的 load
方法调用:
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// 遍历 cats,取出 cls 及 load 对应的 method
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
// 调用 load 方法
(*load_method)(cls, @selector(load));
cats[i].cat = nil;
}
}
}
这里可看到 load
方法的调用时机是比较早的,在 objc
初始化完成的时候就会调用。
- C++函数的调用时机
这里我们实现一段 c++
代码,可以看到这段代码在 main
函数之前,load
函数之后被调用了,通过 lldb
调试,打印函数调用栈信息,可以看到 c++
方法是在 doModInitFunctions
方法之中被调用,上面也讲到了 doModInitFunctions
方法是在 doInitialization
方法中被调用的,而 load
方法是在 notifySingle
中调用的,所以 load
方法在 c++
方法之前被调用。
-
main
函数调用时机
通过打印我们看到main
函数是在load
方法跟c++
方法之后调用,那么main
的调用是什么时候执行的呢?这里我们看一下。
这里我们全局搜索 _dyld_start
方法,看到在 _dyld_start
执行完之后会找到 main
函数的函数指针 jmp
跳转到 main
函数。