iOS 应用程序加载流程分析

在我们的日常开发中我们都知道,应用程序的执行都会依赖一些底层的基础库,例如 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,找到 sNotifyObjCInitregisterObjCNotifiers 函数中被赋值为第二个参数,所以我们搜索 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 方法是由 dylddoModInitFunctions 方法调起。接着我们再回到 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;
                            }
                        }
                    }
                }
            }
        }
    }
}

这里只保留了主要代码,这里主要是循环调用 libSystemlibSystem_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. notifySingledoInitialization 的关联

我们在上面已经讲过了 notifySingle 其实就是间接调用 _dyld_objc_notify_register 函数,并且 _dyld_objc_notify_register 函数调用了 registerObjCNotifiers 函数,并对 sNotifyObjCMappedsNotifyObjCInitsNotifyObjCUnmapped 三个参数进行初始化赋值。但是这三个函数能否调用起来这里还不知道。

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_imagesmap_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 函数。

你可能感兴趣的:(iOS 应用程序加载流程分析)