iOS 底层原理:dyld 应用程序加载

1,008 阅读11分钟

前言

在之前的文章中,我们介绍了对象的数据,属性方法成员变量等。这些都是通过代码实现,都需要加载到内存中我们才能使用,要不然只是文件,今天我们来探索它们是如何加载到应用程序的。

准备工作

一、应用程序的加载原理

每个应用程序加载都需要一些底层的库,UIKitCoreFoundationAVFoundation等等。是可执行的二进制文件,能被操作系统加载到内存,有静态库动态库

编译过程

image.png

可执行文件

1. 项目工程 可执行文件

创建一个macOS的工程:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}
  • 代码就是默认的打印,不做修改。

接下来生成可执行文件,并拖到terminal中:

image.png

  • 如上图,可执行文件拖到终端中是可以执行操作的,打印出了Hello,World!

2. 系统库 可执行文件

查找系统Foundation可执行文件:

image.png

  • 通过image list获取到Foundation可执行文件路径,最终在磁盘中成功找到。

静态链接 和 动态链接

image.png

  • 动态链接方式可以共享动态库,优化了内存空间,所以苹果的库都是动态库。

加载过程

库是通过dyld(动态连接器)加载到内存中来的,整体流程可以用下面的图来表示:

image.png

二、dyld 的引出

我们先创建一个iOS的工程,在ViewController.m中添加load方法:

@implementation ViewController
+ (void)load{
    NSLog(@"%s",__func__);
}
@end

main函数处打一个断点,运行程序:

image.png

  • 程序成功断在了main函数,我们发现在main函数之前还调用了一个start函数,那么先添加一个start的符号断点进行调试。

添加start符号断点,再次运行程序:

image.png

  • 添加的start符号断点并没有断住,程序还是走到了main函数,说明这些符号断点并不是start的实现。在main函数前+[ViewController load]被调用了,那么就在load方法打一个断点进行调试。

ViewControllerload方法打断点,运行程序:

image.png

  • 程序断在load方法后,通过bt打印堆栈,在堆栈中发现了_dyld_start函数。到这里也是引出了dyld,点击 dyld-852 进行源码的下载,接下来进行dyld源码的探索。

三、dyld 流程上

源码中全局搜索_dyld_start

image.png

  • 我们在dyldStartup.s文件中找到了_dyld_start的实现。并且看到了call dyldbootstrap::start这样的代码,dyldbootstrapC++中的命名空间,说明start的实现在这个命名空间下。

dyldbootstrap命名空间中找到start函数:

uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
				const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
    ...
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
  • 可以看到start函数中,返回了_main函数,接下来对_main进行分析。

四、dyld 流程中的 main 函数主流程

点击进入_main函数:

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
		int argc, const char* argv[], const char* envp[], const char* apple[], 
		uintptr_t* startGlue)
{
    ...
    // 整个主程序的一些信息的处理,CPU、架构等
    getHostInfo(mainExecutableMH, mainExecutableSlide);
    
    // 镜像文件的一些处理
    {
        __block bool platformFound = false;
        ((dyld3::MachOFile*)mainExecutableMH)->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
            if (platformFound) {
            halt("MH_EXECUTE binaries may only specify one platform");
            }
            gProcessInfo->platform = (uint32_t)platform;
            platformFound = true;
        });
    }
    
    // 路径相关的一些操作
    const char* rootPath = _simple_getenv(envp, "DYLD_ROOT_PATH");
    if ( (rootPath != NULL) ) {
	...	
    }
    else {
       ...
    }
    
    // 共享缓存加载 load shared cache 
    mapSharedCache(mainExecutableSlide);
    
    // 实例化主程序 instantiate ImageLoader for main executable
    sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
    
    // 加载插入的动态库 load any inserted libraries 
    if	( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
        for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
        loadInsertedDylib(*lib);
    }
    
    // link 主程序
    link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
    
    // 循环递归绑定 Bind and notify for the inserted images now interposing has been registered
    if ( sInsertedDylibCount > 0 ) {
        for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
            ImageLoader* image = sAllImages[i+1];
            image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true, nullptr);
        }
    }
    
    // 弱引用绑定主程序 <rdar://problem/12186933> do weak binding only after all inserted images linked 
    sMainExecutable->weakBind(gLinkContext);
    gLinkContext.linkingMainExecutable = false;
    
    // 初始化 run all initializers
    initializeMainExecutable(); 
    
    // 通知dyld可以进main()函数了 notify any montoring proccesses that this process is about to enter main()
    notifyMonitoringDyldMain();
    
    result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
    
    return result;
}

五、initializeMainExecutable 流程-主程序运行

进入initializeMainExecutable函数:

void initializeMainExecutable()
{
    // run initialzers for any inserted dylibs
    // allImagesCount():拿到所有镜像文件的个数
    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]);
        }
    }
	
    // 主程序初始化 run initializers for main executable and everything it brings up 
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
}
  • 可以发现不管是镜像文件初始化,还是主程序的初始化,都是调用了runInitializers

runInitializers

进入runInitializers函数:

image.png

  • 这个函数的重点是processInitializers函数。

processInitializers

进入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;
    // Calling recursive init on all images in images list, building a new list of
    // uninitialized upward dependencies.
    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);
}
  • 这个函数的重点是recursiveInitialization函数。

recursiveInitialization

进入recursiveInitialization函数:

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
										  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
    {
    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) ) {
                        tUps.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);

            // let objc know we are about to initialize this image
            uint64_t t1 = mach_absolute_time();
            fState = dyld_image_state_dependents_initialized;
            oldState = fState;
            // 单个通知注入
            context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
			
            // 调用init方法 initialize this image
            bool hasInitializers = this->doInitialization(context);
            
            // let anyone know we finished initializing this image
            fState = dyld_image_state_initialized;
            oldState = fState;
            // 通知初始化完成
            context.notifySingle(dyld_image_state_initialized, this, NULL);
        }
        catch (const char* msg) {
            ...
        }
    }
}
  • context.notifySingle:单个通知注入。
  • this->doInitialization:调用init方法。
  • context.notifySingle:通知初始化完成。

notifySingle

全局搜索,找到了notifySingle函数的赋值:

image.png

点进进入notifySingle

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    //dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
    std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
    if ( handlers != NULL ) {
        dyld_image_info info;
	info.imageLoadAddress	= image->machHeader();
	info.imageFilePath		= image->getRealPath();
	info.imageFileModDate	= image->lastModified();
	for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
            const char* result = (*it)(state, 1, &info);
            if ( (result != NULL) && (state == dyld_image_state_mapped) ) {
                //fprintf(stderr, "  image rejected by handler=%p\n", *it);
		// make copy of thrown string so that later catch clauses can free it
		const char* str = strdup(result);
		throw str;
            }
	}
    }
    if ( state == dyld_image_state_mapped ) {
	// <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache
	// <rdar://problem/50432671> Include UUIDs for shared cache dylibs in all image info when using private mapped shared caches
	if (!image->inSharedCache()
            || (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion)) {
            dyld_uuid_info info;
            if ( image->getUUID(info.imageUUID) ) {
		info.imageLoadAddress = image->machHeader();
		addNonSharedCacheImageUUID(info);
            }
	}
    }
    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);
	}
    }
}
  • 定位关键代码(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());

sNotifyObjCInit

搜索查找,得到sNotifyObjCInit的相关代码:

static _dyld_objc_notify_init		sNotifyObjCInit;

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;
}
  • sNotifyObjCInit_dyld_objc_notify_init类型的,在registerObjCNotifiers函数中被赋的值,那么registerObjCNotifiers又是在哪儿里被调用的呢。

registerObjCNotifiers

全局搜索registerObjCNotifiers函数:

image.png

  • _dyld_objc_notify_register函数中找到了registerObjCNotifiers的调用,而_dyld_objc_notify_register我们也是见过的。

看下 objc4-818.2 源码_objc_init函数的实现: image.png

  • 在这里找到了_dyld_objc_notify_register的调用,那么下面我们以_objc_init为切入点继续探索。

六、images 初始化流程

我们接下来,将以_objc_init为切入点,反向推导流程。

打开 objc4-818.2 源码 ,在_objc_init处打断点,运行程序:

image.png

  • 可以看到,在_objc_init函数之前调用了_os_object_init,这个函数在libdispatch库中。

_os_object_init

下载 libdispatch 源码 ,在源码中全局搜索_os_object_init函数:

image.png

  • 找到了_os_object_init函数的实现,并在其中找到了_objc_init()函数的调用,目前得到流程:_os_object_init -> _objc_init()

再看下_os_object_init函数之前调用了什么函数:

image.png

  • 可以看到是libdispatch_init被调用了,它也属于libdispatch库。

libdispatch_init

全局搜索,得到libdispatch_init函数:

libdispatch_init(void)
{
    ...
    _dispatch_hw_config_init();
    _dispatch_time_init();
    _dispatch_vtable_init();
    _os_object_init();
    _voucher_init();
    _dispatch_introspection_init();
}
  • libdispatch_init的实现中,找到了_os_object_init()的调用,目前得到流程:libdispatch_init -> _os_object_init -> _objc_init()

再次分析堆栈,查看libdispatch_init之前的函数调用:

image.png

  • libdispatch_init之前调用了libSystem_initializer函数,libSystem_initializer属于libSystem库,接下来继续验证。

libSystem_initializer

下载 Libsystem 源码 ,在源码中全局搜索libSystem_initializer函数:

image.png

  • 找到了libSystem_initializer函数的实现,并在239行找到了libdispatch_init函数的调用,目前得到流程:libSystem_initializer -> libdispatch_init -> _os_object_init -> _objc_init()

查看libSystem_initializer上一步函数的调用:

image.png

  • 可以看到上一步的调用是doModInitFunctions函数,这次又回到了dyld

doModInitFunctions

找到doModInitFunctions,进入函数:

void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
    if ( fHasInitializers ) {
        const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
        const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
        const struct load_command* cmd = cmds;
        for (uint32_t i = 0; i < cmd_count; ++i) {
            if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
                // macho 中相关 command 的加载
                const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
                const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
                const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
                for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
                    const uint8_t type = sect->flags & SECTION_TYPE;
                    if ( type == S_MOD_INIT_FUNC_POINTERS ) {
                        Initializer* inits = (Initializer*)(sect->addr + fSlide);
                        
                        for (size_t j=0; j < count; ++j) {
                            
                            // 获取各 Initializer 包括 libSystem_initializer
                            Initializer func = inits[j];
                            
                            if ( ! dyld::gProcessInfo->libSystemInitialized ) {
                                // <rdar://problem/17973316> libSystem initializer must run first
                                // libSystem 必须最先加载,否则就会报错
                                const char* installPath = getInstallPath();
                                if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
                                    dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
                            }
                            bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL);
                            {
                                dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
                                // Initializer 包括 libSystem_initializer 的调用
                                func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
                            }
                            if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
                                // now safe to use malloc() and other calls in libSystem.dylib
                                dyld::gProcessInfo->libSystemInitialized = true;
                            }
                        }
                    }
                    else if ( type == S_INIT_FUNC_OFFSETS ) {
                        // 常规的映射等
                        ...
                    }
                }
            }
        }
    }
}
  • 通过函数分析,可以知道libSystem是第一个会被加载的镜像文件,否则会报错。
  • UIKitFoundation等其他的库,都会依赖Runtime的基础、线程的基础、环境基础等,所以要最先加载libSystem
  • Initializer func = inits[j];第一次获取的func就是libSystem_initializer,并通过func(context.argc, context.argv, context.envp, context.apple, &context.programVars);进行调用,接下来查看doModInitFunctions的调用。
  • 目前得到流程:doModInitFunctions -> libSystem_initializer -> libdispatch_init -> _os_object_init -> _objc_init()

搜索doModInitFunctions,查看它的调用:

image.png

  • OK!!,回到了我们上面提到的函数doInitialization
  • 目前得到流程:doInitialization -> doModInitFunctions -> libSystem_initializer -> libdispatch_init -> _os_object_init -> _objc_init()

七、dyld 链接 objc 的函数执行

通过之前的分析,我们得到doInitialization后面的流程如下:

doInitialization -> doModInitFunctions -> libSystem_initializer -> libdispatch_init -> _os_object_init -> _objc_init() -> _dyld_objc_notify_register -> registerObjCNotifiers

再回顾下_dyld_objc_notify_registerregisterObjCNotifiers函数的调用,以及函数实现的关键代码:

void _objc_init(void)
{
    ...
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    ...
}

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}

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;
    ...
}
  • 通过对比分析可以得出,map_images() = sNotifyObjCMapped()load_images() = sNotifyObjCInit()
  • 接下来探索一下map_images()load_images()在哪里被调用了。
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
										  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)

全局搜索sNotifyObjCMapped:

image.png

  • notifyBatchPartial函数中,找到了sNotifyObjCMapped的调用。

全局搜索notifyBatchPartial:

image.png

  • registerObjCNotifiers函数中找到了notifyBatchPartial的调用,原来sNotifyObjCMapped函数在这个函数中被赋值完,就直接被调用了。

sNotifyObjCInit在哪儿里被调用了呢,继续搜索:

image.png

  • notifySingle函数中,找到sNotifyObjCInit的调用。

notifySingle的调用上面已经聊到过,它和doInitialization一样是在recursiveInitialization中被调用的:

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
										  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
    {
    if ( fState < dyld_image_state_dependents_initialized-1 ) {
        try {
            // 单个通知注入
            context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
			
            // 调用init方法 initialize this image
            bool hasInitializers = this->doInitialization(context);
            
            // let anyone know we finished initializing this image
            fState = dyld_image_state_initialized;
            oldState = fState;
            // 通知初始化完成
            context.notifySingle(dyld_image_state_initialized, this, NULL);
        }
        catch (const char* msg) {
            ...
        }
    }
}
  • 看代码context.notifySingle是在this->doInitialization前面就被调用了,而sNotifyObjCInit是在doInitialization才被注册,这是为什么呢?
  • 因为recursiveInitialization是个递归函数,第一次调用notifySinglesNotifyObjCInit没有被初始化,第二次进来时sNotifyObjCInit就有值了。
  • 总结一下,第一次进入时会先调用doInitialization,函数内对map_imagesload_images进行初始化,紧接这调用map_images。然后走到下面的notifySingle函数,会调用load_images函数。

八、load 方法调用

load_images

进入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;

    // Discover load methods
    {
        prepare_load_methods((const headerType *)mh);
    }
    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}
  • prepare_load_methods函数,会做一些准备工作,将主类的load方法和分类的load方法分别加到各自的表中。
  • call_load_methods函数,会对主类load方法和分类的load方法进行调用。

prepare_load_methods

进入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++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        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());
        add_category_to_loadable_list(cat);
    }
}
  • 主类会循环调用schedule_class_load函数,来添加load方法。
  • 分类会循环调用add_category_to_loadable_list函数,来添加load方法。

1. 主类 load方法 添加

进入schedule_class_load

static void schedule_class_load(Class cls)
{
    // 如果为 nil 函数返回
    if (!cls) return;

    // 如果flags为RW_LOADED 说明已经load过,函数返回
    if (cls->data()->flags & RW_LOADED) return;
    
    // 父类递归调用本函数
    schedule_class_load(cls->getSuperclass());

    // 添加到表里
    add_class_to_loadable_list(cls);
    
    // 设置flags为RW_LOADED 标识已经load过
    cls->setInfo(RW_LOADED); 
}

进入add_class_to_loadable_list

void add_class_to_loadable_list(Class cls)
{
    // 获取load 方法的imp
    IMP 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));
    }
    
    // 将cls 和method 赋值到loadable_classes 表中
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}
  • loadable_classes是数组,数组中的元素是loadable_classloadable_class的数据结构: image.png
  • clsmethod通过loadable_class结构进行赋值,每增加一个load就会loadable_classes_used++,用来统计load的数量和方便下次的赋值。

2. 分类 load方法 添加

void add_category_to_loadable_list(Category cat)
{
    IMP 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));
    }
    
    // 将cls 和method 赋值到loadable_categories 表中
    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}
  • 分类load方法添加,和主类的添加方式是一样的,只不过是不同的而已,分类的数组元素是loadable_category,数据结构表示为: image.png

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 {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }
        // 2. Call category +loads ONCE
        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;
}
  • do while循环调用call_class_loads方法,来调用类的load方法。
  • do while循环调用call_category_loads方法,来调用分类的load方法。

1. call_class_loads 调用主类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;
    
    // for 循环遍历主类load表
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        // 将load 方法的imp,从主类load表中取出来
        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方法的imp,传参cls和@selector(load)
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

注意:这里load方法的调用,是直接imp的调用,不是发送消息。

2. call_category_loads 调用分类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;

    // for 循环遍历分类load表
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        // 将load 方法的imp,从分类load表中取出来
        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方法的imp,传参cls和@selector(load)
            (*load_method)(cls, @selector(load));
            cats[i].cat = nil;
        }
    }

    // Compact detached list (order-preserving)
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift++;
        }
    }
    used -= shift;

    // Copy any new +load candidates from the new list to the detached list.
    new_categories_added = (loadable_categories_used > 0);
    for (i = 0; i < loadable_categories_used; i++) {
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)
                realloc(cats, allocated *
                                  sizeof(struct loadable_category));
        }
        cats[used++] = loadable_categories[i];
    }

    // Destroy the new list.
    if (loadable_categories) free(loadable_categories);

    // Reattach the (now augmented) detached list. 
    // But if there's nothing left to load, destroy the list.
    if (used) {
        loadable_categories = cats;
        loadable_categories_used = used;
        loadable_categories_allocated = allocated;
    } else {
        if (cats) free(cats);
        loadable_categories = nil;
        loadable_categories_used = 0;
        loadable_categories_allocated = 0;
    }

    if (PrintLoading) {
        if (loadable_categories_used != 0) {
            _objc_inform("LOAD: %d categories still waiting for +load\n",
                         loadable_categories_used);
        }
    }

    return new_categories_added;
}

注意:这里load方法的调用,也是直接imp的调用,不是发送消息。

九、dyld 流程分析图

image.png

今天是对dyld的大体流程做了分析,下一篇将对加载等详细信息进行探索,点个赞支持一下吧!!😄😄😄。