【iOS】源码学习-类的加载

【iOS】源码学习-类的加载

前言

在App启动阶段,由动态链接器dyld加载程序镜像,再交由Runtime运行时库,一步一步完成数据解析、地址修复、类注册、元数据挂载和类初始化等一系列操作,最终将磁盘中的静态类数据转化为内存中可接收消息、正常调用的类对象。

_objc_init源码解析

Runtime需要提前初始化,整个OC类加载的源头就是_objc_init。

objc 复制代码
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    locks_init();
    environ_init();
    runtime_tls_init();
    _objc_sync_init();
    accessors_init();
    side_tables_init();
    static_init();
    runtime_init();
    exception_init();
    cache_t::init();

#if !TARGET_OS_EXCLAVEKIT
    _imp_implementationWithBlock_init();
#endif

    _dyld_objc_callbacks_v4 callbacks = {
        4, // version
        map_images,
        load_images,
        unmap_image,
        _objc_patch_root_of_class,
    };
    _dyld_objc_register_callbacks((_dyld_objc_callbacks*)&callbacks);

    didCallDyldNotifyRegister = true;
}

_objc_init隶属于底层动态库libobjc.A.dylib。在App进程启动早期,dyld装载主程序与所有依赖动态库,随后libSystem初始化,由libSystem间接调用_objc_init,提前启动Runtime环境。

执行顺序:进程启动->dyld加载动态库->libSystem初始化->_objc_init()->注册dyld镜像回调->执行map_images/load_images->main函数。

_objc_init内部依次执行八大初始化函数,完成运行时环境搭建、异常捕获、线程初始化、注册dyld回调。

environ_init

环境变量初始化。读取并解析App运行环境变量,根据不同的环境变量控制Runtime的调试模式、内存检测、日志打印能力。

objc 复制代码
void environ_init(void) 
{
#if !TARGET_OS_EXCLAVEKIT
    if (issetugid()) {
        // All environment variables are silently ignored when setuid or setgid
        // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
        return;
    } 

    // Turn off autorelease LRU coalescing by default for apps linked against
    // older SDKs. LRU coalescing can reorder releases and certain older apps
    // are accidentally relying on the ordering.
    // rdar://problem/63886091
//    if (!dyld_program_sdk_at_least(dyld_fall_2020_os_versions))
//        DisableAutoreleaseCoalescingLRU = On;

    // class_rx_t pointer signing enforcement is *disabled* by default unless
    // this OS feature is enabled, but it can be explicitly enabled by setting
    // the environment variable, for testing.
//    if (!os_feature_enabled_simple(objc4, classRxSigning, false))
//        DisableClassRXSigningEnforcement = On;

    // Faults for class_ro_t pointer signing enforcement are disabled by
    // default unless this OS feature is enabled.
//    if (!os_feature_enabled_simple(objc4, classRoSigningFaults, false))
//        DisableClassROFaults = On;

#if TARGET_OS_OSX || TARGET_OS_SIMULATOR
//    if (!os_feature_enabled_simple(objc4, autoreleaseFaultsMacOS, false))
//        DisableFaults = On;
#endif
#endif // !TARGET_OS_EXCLAVEKIT

    bool PrintHelp = false;
    bool PrintOptions = false;
    bool maybeMallocDebugging = false;

    // Scan environ[] directly instead of calling getenv() a lot.
    // This optimizes the case where none are set.
    char **envp = NULL;
#if TARGET_OS_EXCLAVEKIT
    if (_objc_test_get_environ)
        envp = _objc_test_get_environ();
#else
    envp = *_NSGetEnviron();
#endif
    if (!envp)
        return;

  // 遍历所有环境变量,初始化对应调试开关
    for (char **p = envp; *p != nil; p++) {
        if (0 == strncmp(*p, "Malloc", 6)  ||  0 == strncmp(*p, "DYLD", 4)  ||  
            0 == strncmp(*p, "NSZombiesEnabled", 16))
        {
            maybeMallocDebugging = true;
        }

        if (0 != strncmp(*p, "OBJC_", 5)) continue;
        
        if (0 == strncmp(*p, "OBJC_HELP=", 10)) {
            PrintHelp = true;
            continue;
        }
        if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {
            PrintOptions = true;
            continue;
        }
        
        if (0 == strncmp(*p, "OBJC_DEBUG_POOL_DEPTH=", 22)) {
            SetPageCountWarning(*p + 22);
            continue;
        }

        const char *value = strchr(*p, '=');
        if (!*value) continue;
        value++;
        
        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];
#if !TARGET_OS_EXCLAVEKIT
            if (opt->internal
                && !os_variant_allows_internal_security_policies("com.apple.obj-c"))
                continue;
#endif // !TARGET_OS_EXCLAVEKIT
            if ((size_t)(value - *p) == 1+opt->envlen  &&  
                0 == strncmp(*p, opt->env, opt->envlen))
            {
                if (strcasecmp(value, "fatal") == 0
                    || strcasecmp(value, "halt") == 0)
                    *opt->var = Fatal;
                else if (strcasecmp(value, "fault") == 0)
                    *opt->var = Fault;
                else if (strcasecmp(value, "stochastic-fault") == 0
                         || strcasecmp(value, "stochasticFault") == 0)
                    *opt->var = StochasticFault;
                else if (strcasecmp(value, "yes") == 0
                         || strcasecmp(value, "warn") == 0
                         || strcasecmp(value, "true") == 0
                         || strcasecmp(value, "on") == 0
                         || strcasecmp(value, "y") == 0
                         || strcmp(value, "1") == 0)
                    *opt->var = On;
                else
                    *opt->var = Off;
                break;
            }
        }
    }

#if !TARGET_OS_EXCLAVEKIT
    // Special case: enable some autorelease pool debugging
    // when some malloc debugging is enabled 
    // and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO.
    if (maybeMallocDebugging) {
        const char *insert = getenv("DYLD_INSERT_LIBRARIES");
        const char *zombie = getenv("NSZombiesEnabled");
        const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION");
        if ((getenv("MallocStackLogging")
             || getenv("MallocStackLoggingNoCompact")
             || (zombie && (*zombie == 'Y' || *zombie == 'y'))
             || (insert && strstr(insert, "libgmalloc")))
            && !pooldebug) {
            DebugPoolAllocation = On;
        }
    }

//    if (!os_feature_enabled_simple(objc4, preoptimizedCaches, true)) {
//        DisablePreoptCaches = On;
//    }
#endif // !TARGET_OS_EXCLAVEKIT

    // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    if (PrintHelp  ||  PrintOptions) {
        if (PrintHelp) {
            _objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
            _objc_inform("OBJC_HELP: describe available environment variables");
            if (PrintOptions) {
                _objc_inform("OBJC_HELP is set");
            }
            _objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
        }
        if (PrintOptions) {
            _objc_inform("OBJC_PRINT_OPTIONS is set");
        }

        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];
#if !TARGET_OS_EXCLAVEKIT
            if (opt->internal
                && !os_variant_allows_internal_security_policies("com.apple.obj-c"))
                continue;
#endif // !TARGET_OS_EXCLAVEKIT
            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
            if (PrintOptions) {
                switch (*opt->var) {
                case Off:
                    break;
                case On:
                    _objc_inform("%s is set", opt->env);
                    break;
                case Fatal:
                    _objc_inform("%s is fatal", opt->env);
                    break;
                case Fault:
                    _objc_inform("%s is faulting", opt->env);
                    break;
                case StochasticFault:
                    _objc_inform("%s is stochastically faulting", opt->env);
                    break;
                }
            }
        }
    }
}

常见的开发常用环境变量:

  • DYLD_PRINT_STATISTICS:设置为YES后,控制台就会打印App的加载时长(整体加载时长+动态库加载时长),即main函数之前的启动时间。可以通过通过设置了解其耗时部分,进而对其进行启动优化。
  • OBJC_DISABLE_NONPOINTER_ISA:禁用生成相应的nonpointer isa,只能生成普通的isa。
  • OBJC_PRINT_LOAD_METHODS:打印class和category的+(void)load方法的调用信息。
  • NSDoubleLocalizedStrings/NSShowNonLocalizedStrings:国际化调试专用,检测未本地化字符串。

tls_init

本地线程初始化。初始化Runtime内部的本地线程池,创建线程私有Key,并绑定线程销毁回调函数,统一管理OC线程资源,保障autoreleasepool、线程私有数据的正常运行。

objc 复制代码
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS // 本地线程池,用来进行处理
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific); // 初始init
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific); // 析构
#endif
}

static_init

静态构造函数初始化。由于libobjc属于动态库,在dyld调用动态库静态构造函数前,libc会优先触发objc_init。因此Runtime内部的系统级C++静态构造函数需要手动遍历执行。系统级静态构造函数的优先级大于开发者自定义静态构造函数。

objc 复制代码
__attribute__((noinline))
static void static_init()
{
    size_t count;
    auto offsets = getLibobjcInitializerOffsets(&count);
    for (size_t i = 0; i < count; i++) {
        UnsignedInitializer init(offsets[i]);
#if DEBUG || __has_feature(address_sanitizer)
        init();
#else
        _objc_inform("libobjc static initializer found: %p %s at libobjc + %" PRIu32, init.address(), init.debugName(), offsets[i]);
#endif
    }
#if DEBUG
    if (count == 0)
        _objc_inform("No static initializers found in libobjc. This is unexpected for a debug build. Make sure the 'markgc' build phase ran on this dylib. This process is probably going to crash momentarily due to using uninitialized global data.");
#elif __has_feature(address_sanitizer)
    // Don't check either way for ASan builds.
#else
    if (count != 0)
        _objc_fatal("error: libobjc release build forbids static initializers, found %zu.", count);
#endif
}

runtime_init

运行时核心初始化,主要初始化类和类的表。

objc 复制代码
void runtime_init(void)
{
    // 初始化OC类、方法的扫描器
    objc::Scanner::init();
    // 配置类签名安全校验开关
    objc::disableEnforceClassRXPtrAuth = DisableClassRXSigningEnforcement;
    // 初始化一个全局表,用来存储附加分类待附加分类的全局哈希表
    objc::unattachedCategories.init(32);
    // 把动态分配类的全局集合
    objc::allocatedClasses.init();
}

exception_init

异常捕获初始化。初始化libobjc全局异常处理系统,注册异常处理的回调,监控异常的处理。

objc 复制代码
void exception_init(void)
{
  // std::set_terminate:C++标准库函数,用于设置程序未捕获异常的终止回调函数
  // 当应用发生崩溃、出现未捕获异常时,流程优先进入_objc_terminate方法
    old_terminate = std::set_terminate(&_objc_terminate);
}

当有异常发生时,会调用_objc_terminate方法。该方法会区分异常类型,并调用应用层注册的异常处理钩子,最终再执行原始的系统终止逻辑。

objc 复制代码
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }

    if (! __cxa_current_exception_type()) {
        // No current exception. // 不存在当前异常,直接执行原有终止逻辑
        (*old_terminate)();
    }
    else {
        // There is a current exception. Check if it's an objc exception.
        @try {
          // 存在未捕获异常,判断异常类型
            __cxa_rethrow();
        } @catch (id e) {
            // It's an objc object. Call Foundation's handler, if any. // 判定为OC异常,调用底层异常处理回调,执行系统原始终止逻辑
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // It's not an objc object. Continue to C++ terminate. // 非OC异常,走默认终止流程
            (*old_terminate)();
        }
    }
}

上面提到的应用层异常钩子注册是指objc_setUncaughtExceptionHandler。当开发者在App里调用NSSetUncaughtExceptionHandler(自定义函数);时,底层调用:

objc 复制代码
objc_uncaught_exception_handler 
  // fn:应用开发者自定义的异常处理函数
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}

Runtime提供全局函数,允许应用层自定义未捕获异常的处理逻辑,本质是对外暴露uncaught_handler全局指针的读写能力。

异常层级划分:

  • Mach异常(内核层):系统最底层异常。一般不会直接处理,会自动转换成Unix信号。
  • Unix信号(系统层):日常绝大多数异常的来源。常见信号有:
    • SIGABRT:主动崩溃
    • SIGSEGV:野指针,内存访问错误
    • SIGBUGS:内存对齐错误
  • NSException(应用层):OC未捕获异常,Runtime会调用abort发送SIGABRT信号造成崩溃。

cache_init

方法缓存初始化。初始化方法缓存相关全局配置、缓存内存池,为后续objc_msgSend消息发送、方法缓存命中提供底层支撑。

_imp_implementationWithBlock_init

兼容层初始化。负责处理启动回调机制,这个方法的作用是将一个block转换为一个OC的方法实现IMP。负责tramPoline的动态库初始化,正常情况是懒加载的。做一个兼容性处理,即趁着沙盒还没收紧之前,将后面必须的库先装进内存。

tramPoline:是一段小型汇编代码,用于将未实现的方法调用转发给消息转发机制。是OC消息转发机制的核心入口。

objc 复制代码
void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
    // Eagerly load libobjc-trampolines.dylib in certain processes. Some
    // programs (most notably QtWebEngineProcess used by older versions of
    // embedded Chromium) enable a highly restrictive sandbox profile which
    // blocks access to that dylib. If anything calls
    // imp_implementationWithBlock (as AppKit has started doing) then we'll
    // crash trying to load it. Loading it here sets it up before the sandbox
    // profile is enabled and blocks it.
    //
    // This fixes EA Origin (rdar://problem/50813789)
    // and Steam (rdar://problem/55286131)
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") == 0 ||
         strcmp(__progname, "Steam Helper") == 0)) {
        // 初始化tramPolin子系统,提前加载libobjc-trampolines.dylib
        // libobjc-trampolines.dylib:是系统专门放trampoline汇编代码的私有动态库,所有OC消息转发、Block转IMP的跳板在这里
        Trampolines.Initialize();
    }
#endif
}

_dyld_objc_notify_register

注册dyld三大回调函数。仅objc运行时使用,向dyld注册镜像生命周期回调,打通dyld与Runtime,正式开启类加载流程。注册的三个回调,对应镜像完整生命周期:

  • map_images(类加载) :镜像载入内存时触发,完成类加载。
    Runtime类加载的核心入口。作用是遍历所有加载完成的镜像,解析Mach-O中所有OC元数据,完成SEL修复、类读取、协议注册、分类挂载、非懒加载类初始化等操作。所有类从磁盘载入内存的全过程都在此阶段。
  • load_images(非类加载,仅执行方法):镜像初始化完毕,执行所有+load。
    在map_images执行完毕后,所有镜像及内部OC元数据全部加载完成后触发。该方法和类加载无关,唯一作用在于遍历所有类与分类,按照既定规则执行+load方法。
  • unmap_images(销毁阶段):镜像卸载,资源回收。
    当App进程退出、动态库手动卸载时触发。作用时逆序卸载镜像、释放内存资源、注销全局注册的类、协议、SEL,销毁分类挂载的元数据,防止内存泄漏。

总结一下:

_objc_init的本质就是初始化Runtime运行环境和注册dyld回调。App启动并不是直接加载类,而是初始化线程异常、环境变量、全局表,最后注册回调,dyld后续才会依次回调map_images、load_images,最终完成类加载,最后执行main函数。

类如何加载到内存

类加载到内存的过程就是:代码通过编译,读取到Mach-O可执行文件中,再从Mach-O中读取到内存。

map_images方法的主要作用是将Mach-O中的类信息加载到内存。

map_images源码:

objc 复制代码
void
// 映射镜像文件,加载新的OC镜像
// p1:本次要加载的镜像数量
// p2:镜像信息数组,每个元素都是一个dyld传递的镜像描述
// p3:函数指针,用于把镜像内存改为可写
map_images(unsigned count, const struct _dyld_objc_notify_mapped_info infos[],
           _dyld_objc_mark_image_mutable makeImageMutable)
{
    // 标记是否需要触发
    bool takeEnforcementDisableFault;

    // 加锁作用域,执行完自动释放锁
    {
        // 加互斥锁runtimeLock:OC运行时全局锁,保证线程安全
        mutex_locker_t lock(runtimeLock);
        // 调用无锁版核心处理函数:真正执行镜像加载、类注册、分类加载
        // 传入参数:镜像数量、镜像信息、错误标记、可写函数
        map_images_nolock(count, infos, &takeEnforcementDisableFault, makeImageMutable);
    } // 锁在这里自动释放

    // 安全校验处理
    if (takeEnforcementDisableFault) {
        if (DebugClassRXSigning == Fatal)
            _objc_fatal("class_rx signing mismatch");

#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
        if (!DisableClassROFaults)
            _objc_fault("class_ro_t enforcement disabled");
#endif
    }
}

map_images_nolock源码:

objc 复制代码
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    // ...省略

    // Find all images with Objective-C metadata.查找所有带有Objective-C元数据的映像
    hCount = 0;

    // Count classes. Size various table based on the total.计算类的个数
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    // 代码块:进行局部处理,即局部处理一些事件
    {
        // ...省略
    }
    
    // ...省略

    if (hCount > 0) {
        // 加载镜像文件
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // Call image load funcs after everything is set up.一切设置完成后,调用镜像加载功能。
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }
}

_read_images源码:

这个函数是真正处理类加载的核心函数,主要是加载类信息,包括类、分类、协议等。主要执行以下几步:

  1. 条件控制进行的一次加载

主要通过NSCreateMapTable创建表,存放类信息,即创建一张类的哈希表,为了类查找方便快捷。

objc 复制代码
if (!doneOnce) {
  
  //...省略
    
  // namedClasses
  // Preoptimized classes don't go in this table.
  // 4/3 is NXMapTable's load factor
  int namedClassesSize = 
      (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
  // 创建哈希表,目的是查找快
  // gdb_objc_realized_classes:用于存储不在共享缓存且已命名类,无论类是否实现,其容量都是类数量的4/3
  gdb_objc_realized_classes =
      NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

  ts.log("IMAGE TIMES: first time tasks");
}
  1. 修复预编译阶段的@selector的混乱问题

主要通过selrefs拿到Mach-O中的静态段__objc_selrefs,然后通过遍历列表调用sel_registerNameNoLock将SEL添加到哈希表nameSelectors中。

objc 复制代码
// 全局统计总共修复多少个未绑定的SEL
// SEL不是简单的字符串,而是带地址的字符串
static size_t UnfixedSelectors;
{
    // 加锁,操作SEL全局表,必须线程安全
    mutex_locker_t lock(selLock);
    // 遍历所有刚加载进来的镜像
    for (auto& info : infos) {
        // 如果dyld已经优化过这个镜像的SEL,直接跳过
        if (info.dyldObjCRefsOptimized()) continue;

        // 判断是不是插件/动态库
        bool isBundle = info.hi->isBundle();
        // 拿到Mach-O中的静态段__objc_selrefs,里面是所有 @selector()的指针数组
        SEL *sels = info.hi->selrefs(&count);
        UnfixedSelectors += count;
        for (i = 0; i < count; i++) {
            const char *name = sel_cname(sels[i]);
            // 去全局表里注册这个名字:如果表中已有,返回已存在的SEL;如果没有,创建一个新的存进去。保证相同名字永远返回同一个内存地址
            SEL sel = sel_registerNameNoLock(name, isBundle);
            // Mach-O里存的SEL地址不是个=全局表的正确地址,说明需要修复
            if (sels[i] != sel) {
                // The infos array is reversed, but dyld expects the original index
                // 计算镜像下标:因为dyld内部顺序反转,所以要倒一下
                const uint32_t infoIndex = (hCount - 1) - infos.index(&info);

                // 把镜像内存改成可写,因为系统镜像默认是只读的
                makeImageMutable(infoIndex);
                // 修复内存:把错误的SEL地址改成全局唯一正确的地址
                withMutableSharedCache(info.tproEnabled(), [&] {
                    sels[i] = sel;
                });
            }
        }
    }
}
objc 复制代码
SEL sel_registerNameNoLock(const char *name, bool copy) {
    return __sel_registerName(name, 0, copy);  // NO lock, maybe copy
}
objc 复制代码
// p1:方法名字符串 p2:是否需要加锁 p3:是否需要复制字符串
static SEL __sel_registerName(const char *name, bool shouldLock, bool copy)
{
    SEL result = 0;

    // 检查当前锁状态是否正确
    if (shouldLock) lockdebug::assert_unlocked(&selLock.get());
    else            lockdebug::assert_locked(&selLock.get());

    if (!name) return (SEL)0;

    // 先查内置SEL(比如alloc、init、release、retain这些系统方法)。它们是固定地址,不用存哈希表
    result = _sel_searchBuiltins(name);
    if (result) return result;
    
    // 如果不是内置方法就操作全局哈希表
    conditional_mutex_locker_t lock(selLock, shouldLock);
    // 核心操作:往全局哈希表中插入name。如果表中已有这个名字,返回已存在的迭代器;反之插入新的并返回
	auto it = namedSelectors.get().insert(name);
	if (it.second) {
		// No match. Insert.
        // 本次新插入的需要分配内存,把字符串存下来
		*it.first = (const char *)sel_alloc(name, copy);
	}
    // 返回全局唯一的SEL地址
	return (SEL)*it.first;
}

要确保返回全局唯一的SEL地址。SEL字符串可以一致,但地址必须不一致

  1. 修复错误混乱的类

主要是从Mach-O取出所有类,再遍历进行处理,修复dyld优化而错误混乱的类。

因为dyld共享缓存使得系统把所有类提前编译好、放缓存里、直接给地址,不加载完整信息,因此会出现三种在内存里残缺、占位、错乱、不完整的类:

  • 只有地址,没有名字的类,即未来类。

未来类:编译时只有类的声明,没有类的实现;运行时才补全实现的类。

  • 地址被缓存改写过的类
  • 被用户重写覆盖的系统类
objc 复制代码
// 判断是否有镜像被dyld覆盖/重写,即判断是否需要走完整类加载逻辑
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

for (auto info : infos) {
    // 遍历所有镜像信息,判断是否必须读取类
    // mustReadClasses:返回bool类型,返回false表示镜像已被dyld极致优化,无需手动加载类
    if (! mustReadClasses(info, hasDyldRoots)) {
        // Image is sufficiently optimized that we need not call readClass()
        continue;
    }

    // 获取当前镜像类里的所有类的指针数组和类的总数
    classref_t const *classlist = info.hi->classlist(&count);

    // 当前镜像是不是Bundle
    bool headerIsBundle = info.hi->isBundle();
    // 当前镜像是否被dyld预优化过
    bool headerIsPreoptimized = info.dyldObjCRefsOptimized();

    // 遍历镜像里的每个类
    for (i = 0; i < count; i++) {
        Class cls = (Class)classlist[i];
        // readClass:加载类到Runtime
        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;
        }
    }
}

这个修复方法的核心是readClass方法。

readClass:主要是读取类,将cls从一个地址变成一个类的名称,即将地址、名称信息存入类。其核心步骤是:取出类名->处理未来类->把类注册到Runtime全局表->返回修复好的类。

objc 复制代码
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();

    // 当前类中如果有丢失的weak-linked类,则返回nil
    if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked).
        // Disavow any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass",
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->setSuperclass(nil);
        return nil;
    }

    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (mangledName != nullptr) {
        // 正常情况下不执行popFutureNamedClass,因为这是专门针对未来类的操作
        if (Class newCls = popFutureNamedClass(mangledName)) {
            // This name was previously allocated as a future class.
            // Copy objc_class to future class's struct.
            // Preserve future's rw data block.

            if (newCls->isAnySwift()) {
                _objc_fatal("Can't complete future class request for '%s' "
                            "because the real class is too big.",
                            cls->nameForLogging());
            }

            // 读取class的data,设置ro、rw
            class_rw_t *rw = newCls->data();
            const class_ro_t *old_ro = rw->ro();

            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());
            memcpy(&newCls->cache, &cls->cache, sizeof(newCls->cache));
            if (cls->hasCustomRR())
                newCls->setHasCustomRR();
            else
                newCls->setHasDefaultRR();
            rw->set_ro(cls->safe_ro());

            freeIfMutable((char *)old_ro->getName());
            free((void *)old_ro);

            addRemappedClass(cls, newCls);

            replacing = cls;
            cls = newCls;
        }
    }

    // 判断类是否已经加载到内存
    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        if (mangledName) { //some Swift generic classes can lazily generate their names
            // 加载共享缓存中的类
            addNamedClass(cls, mangledName, replacing);
        } else {
            Class meta = cls->ISA();
            const class_ro_t *metaRO = meta->bits.safe_ro();
            ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
            ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
        }
        // 插入表,即相当于从Mach-O文件中读取到内存中
        addClassTableEntry(cls);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        const_cast<class_ro_t *>(cls->safe_ro())->flags |= RO_FROM_BUNDLE;
        const_cast<class_ro_t *>(cls->ISA()->safe_ro())->flags |= RO_FROM_BUNDLE;
    }

    return cls;
}
  • 普通链接(强链接):代码引用了一个类/函数/符号,运行时必须存在。否则dyld直接报错,程序启动失败。
  • weak-linked(弱链接):编译时标记这个符号是可选的,运行时有这个符号就正常用;没有也不崩溃,视为nil,不会崩溃。
  1. 修复重映射一些没有被镜像文件加载进来的类

重映射:把旧的、错的、占位的类地址替换成Runtime里正确的新地址。

这里核心方法是remapClassRef,主要是重映射Class和Super Class这两个类:

  • classrefs是获取Mach-O中的静态段__objc_classrefs,即类的引用
  • superrefs是获取Mach-O中的静态段__objc_superrefs,即父类的引用

静态段:是编译阶段固化在Mach-O中的数据段,初始化信息固定,运行时一般不能修改的地址。

  • __objc_classrefs:存储代码中引用的其他类地址
  • __objc_superrefs:存储各类对应的父类的地址
objc 复制代码
// Fix up remapped classes // 修正重新映射的类
// Class list and nonlazy class list remain unremapped. // 类列表和非惰性类列表保持未映射
// Class refs and super refs are remapped for message dispatching. // 类引用和超级引用将重新映射以进行消息分发

// dtrace probe
OBJC_RUNTIME_REMAP_CLASSES_START();

// 如果有类地址需要修复才调用这里
if (!noClassesRemapped()) {
    for (const auto& info : infos) {
        // The infos array is reversed, but dyld expects the original index
        const uint32_t infoIndex = (hCount - 1) - infos.index(&info);
        // 修复所有类引用的地址
        Class *classrefs = info.hi->classrefs(&count);
        for (i = 0; i < count; i++) {
            remapClassRef(&classrefs[i], infoIndex, info.tproEnabled(), makeImageMutable);
        }
        // fixme why doesn't test future1 catch the absence of this?
        // 修复所有父类引用的地址
        classrefs = info.hi->superrefs(&count);
        for (i = 0; i < count; i++) {
            remapClassRef(&classrefs[i], infoIndex, info.tproEnabled(), makeImageMutable);
        }
    }
}

// dtrace probe
OBJC_RUNTIME_REMAP_CLASSES_END();
  1. 修复一些消息

主要是通过messagerefs获取Mach-O的静态段__objc_msgrefs,并通过fixupMessageRef将函数指针修改注册为新的正确的函数指针。

objc 复制代码
#if SUPPORT_FIXUP
// Fix up old objc_msgSend_fixup call sites

// dtrace probe
OBJC_RUNTIME_FIXUP_VTABLES_START();

for (auto info : infos) {
    message_ref_t *refs = info.hi->messagerefs(&count);
    if (count == 0) continue;

    if (PrintVtables) {
        _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                     "call sites in %s", count, info.hi->fname());
    }
    for (i = 0; i < count; i++) {
        // 把旧的、错的方法调用地址修复成运行时正确的函数地址
        fixupMessageRef(refs+i);
    }
}

// dtrace probe
OBJC_RUNTIME_FIXUP_VTABLES_END();
#endif
  1. 当类里面有协议时,readProtocol读取协议protocollist,把地址变成完整的协议结构,然后初始化协议并注册到Runtime全局表。
objc 复制代码
// dtrace probe
OBJC_RUNTIME_DISCOVER_PROTOCOLS_START();

for (auto &info : infos) {
    extern objc_class OBJC_CLASS_$_Protocol;
    Class cls = (Class)&OBJC_CLASS_$_Protocol;
    ASSERT(cls);
    // 创建protocol哈希表
    NXMapTable *protocol_map = protocols();
    bool isPreoptimized = info.dyldObjCRefsOptimized();

    // 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",
                         info.hi->fname());
        }
        continue;
    }

    bool isBundle = info.hi->isBundle();

    // The infos array is reversed, but dyld expects the original index
    const uint32_t infoIndex = (hCount - 1) - infos.index(&info);

    // 从静态段中取出当前镜像里的所有协议列表
    protocol_t * const *protolist = info.hi->protocollist(&count);
    for (i = 0; i < count; i++) {
        // 与readClass一样,把协议从只有地址的占位符变成真正的协议修复协议的错地址,并注册打了Runtime全局表
        readProtocol(protolist[i], cls, protocol_map,
                     isPreoptimized, isBundle,
                     infoIndex, info.tproEnabled(), makeImageMutable);
    }
}

// dtrace probe
OBJC_RUNTIME_DISCOVER_PROTOCOLS_END();
  1. 修复没有被加载的协议

主要是通过protocolrefs获取到Mach-O的静态段__objc_protorefs,然后遍历需要修复的协议,通过remapProtocolRef比较当前协议和协议列表中的同一内存地址的协议是否相同,如果不同就替换。

objc 复制代码
// dtrace probe
OBJC_RUNTIME_FIXUP_PROTOCOLS_START();

for (const auto& info : infos) {
    // The infos array is reversed, but dyld expects the original index
    const uint32_t infoIndex = (hCount - 1) - infos.index(&info);
    if (launchTime && info.hi->isPreoptimized()) {
        // At launch time, we know preoptimized image refs are pointing at the
        // shared cache definition of a protocol.  We can skip the check on
        // launch, but have to visit @protocol refs for shared cache images
        // loaded later.
    } else {
        // 获取到Mach-O的静态段__objc_protorefs
        protocol_t **protolist = info.hi->protocolrefs(&count);
        for (i = 0; i < count; i++) {
            // 比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换
          remapProtocolRef(&protolist[i], infoIndex, info.tproEnabled(), makeImageMutable);
        }
    }
}

// dtrace probe
OBJC_RUNTIME_FIXUP_PROTOCOLS_END();

remapProtocolRef源码:

objc 复制代码
// 替换协议的错误地址
static size_t UnfixedProtocolReferences;
static void remapProtocolRef(protocol_t **protoref,
                             uint32_t objcImageIndex,
                             bool tproEnabled,
                             _dyld_objc_mark_image_mutable makeImageMutable)
{
    lockdebug::assert_locked(&runtimeLock.get());

    // 获取协议列表中统一内存地址的协议
    protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);
    // 如果当前协议与同一内存地址协议不同就替换
    if (*protoref != newproto) {
        makeImageMutable(objcImageIndex);
        withMutableSharedCache(tproEnabled, [&] {
            *protoref = newproto;
        });
        UnfixedProtocolReferences++;
    }
}
  1. 分类处理

主要是处理分类,分类要延后加载,不能太早,也不能太晚。分类依赖主类,Runtime规定需要在分类初始化并将数据加载到类后才执行,并推迟到对_dyld_objc_notify_register的调用完成后的第一个load_images调用后加载,即等dyld通知Runtime所有镜像全部加载完毕后。

objc 复制代码
// dtrace probe
OBJC_RUNTIME_DISCOVER_CATEGORIES_START();

// 遍历所有镜像找到里面所有的分类,把分类的方法、属性赋值到类上,这样才能调用方法生效
if (didInitialAttachCategories) {
    // We attach categories in two phases: first images with preattached
    // categories, then images without. We need to avoid this scenario:
    // 1. Class C still has a preattached categories list.
    // 2. Library A has a non-preattached category on C. We copy C's
    //    preattached lists to the heap and append A's list.
    // 3. Library B has a preattached category on C. Because C no longer
    //    has preattached lists, we append B's list.
    // Step 2 already copied B's list, so we end up with two copies of it.
    // This is usually mostly harmless (just a performance issue) but it
    // can cause real problems for code using class_copyMethodList or
    // similar, as it will see two copies of the entries in the duplicate
    // list.
    // 第一次加载优化过的类
    for (auto info : infos) {
        if (info.dyldCategoriesOptimized())
            load_categories_nolock(info.hi);
    }
    // 第二次加载没优化过的类
    for (auto info : infos) {
        if (!info.dyldCategoriesOptimized())
            // 把分类里的实例方法、类方法、属性、协议全部附加到对应的主类上
            load_categories_nolock(info.hi);
    }
    // 先加载系统优化好的分类再加载普通分类的原因是防止分类重复、方法重复或崩溃
}

// dtrace probe
OBJC_RUNTIME_DISCOVER_CATEGORIES_END();

这里核心方法是load_categories_nolock,其中主要是通过调用attachCategories,attachCategories方法中主要是调用attachToClass。

  • attachCategories:它的作用在于把一批category的方法、属性和协议附加到目标类或元类的运行时扩展区class_rw_ext_t中。
  1. 准备工作:它先根据调试开关打印category覆盖方法和连接日志,然后为目标类创建class_rw_ext_t。
  2. 高效合并优化:遍历cats_list中的每个category,按当前处理的是类还是元类分别取出分类的实例方法、类方法、属性和协议。这些列表不会逐个元素拷贝,而是以method_list_t *、property_list *、protocol_list_t *为单位暂存到固定大小的栈缓冲区中,缓冲区满64个指针就批量attach到rwe->methods/properties/protocols,减少内存开销与写入次数。
  3. 关键逻辑:为了category覆盖顺序正确,它按照"前向遍历、反向填充缓冲区、attachLists前插"的方式组织列表,使后加载或优先级更高的category方法排在方法查找前面,确保方法查找时优先命中。
  4. 前置校验与准备:方法列表在附加前会调用prepareMethodLists()做selector规范化、校验、排序等准备。如果是给已存在类附加category,并且缺失新增了方法,还会刷新相关方法缓存,避免旧缓存继续命中被category替换前的IMP。
  5. 最终合并:最终这个函数完成的是category元数据到类运行时结构的合并。把category方法加入rwe->methods,属性加入rwe->properties,协议加入rwe->protocols,从而让目标类具备category扩展出来的行为和元数据。
  • attachToClass:将分类添加到主类中。
  1. 类的加载处理

主要是实现类的加载处理,实现非懒加载类。

懒加载类:第一次使用的时候才去realizeClass,不用就不加载。

非懒加载类:程序一启动就马上realizeClass。

主要步骤有:

  • 通过nlclslist获取Mach-O的静态段__objc_nlclslist非懒加载类表。
  • 通过addClassTableEntry将非懒加载类插入类表,存储到内存。如果已经添加就不会再添加,需要确保整个结构都被添加。
  • 通过realizeClassWithoutSwift实现当前的类。前面修复错误混乱类时,readClass读取的内存只有地址和名称,类的data数据没有被加载出来,这里把类从空壳变成完整结构,加载ro、rw数据,挂载分类,让类可以真正使用。

realizeClassWithoutSwift核心作用在于把Mach-O中的类原始数据data加载到内存,变成可用的Class对象,并建立完整的继承链与元类链。只处理OC类,不涉及Swift部分

实现主要有以下几步:

  • 读取data数据,并设置ro、rw:读取class的data数据,并将其强制转换为ro,且初始化rw,将ro中的数据拷贝一份到rw的ro中(rw具有动态性,因此对额外信息的类分配一份rwe并将其放入类中供其使用)。
  • 递归调用realizeClassWithoutSwift完善继承链:递归调用realizeClassWithoutSwift设置父类、元类,设置父类和元类的isa指向。通过addSubClass和addRootClass设置父子的双向链表指向关系,即父类中可以找到子类,子类中可以找到父类。在realizeClassWithoutSwift递归调用时,isa找到根元类后,根元类的isa指向自己,并不会返回nil。如果类不存在或类已经实现则返回nil。
  • 调用methodizeClass方法化类:把ro中的方法、属性、协议正式挂到rw的方法列表、属性列表、协议列表里。

这样类就真正具备了接收消息的能力,objc_msgSend才能找到方法。

objc 复制代码
// dtrace probe
OBJC_RUNTIME_REALIZE_NON_LAZY_CLASSES_START();

for (auto info : infos) {
    classref_t const *classlist = info.hi->nlclslist(&count);
    for (i = 0; i < count; i++) {
        Class cls = remapClass(classlist[i]);
        if (!cls) continue;

        addClassTableEntry(cls);

        if (cls->isSwiftStable()) {
            if (cls->swiftMetadataInitializer()) {
                _objc_fatal("Swift class %s with a metadata initializer "
                            "is not allowed to be non-lazy",
                            cls->nameForLogging());
            }
            // fixme also disallow relocatable classes
            // We can't disallow all Swift classes because of
            // classes like Swift.__EmptyArrayStorage
        }
        realizeClassWithoutSwift(cls, nil);
    }
}

// dtrace probe
OBJC_RUNTIME_REALIZE_NON_LAZY_CLASSES_END();
  1. 处理没有被处理的类,优化被侵犯的类

被侵犯的类是指原本应该懒加载,现在被提前用或修改的类。如CoreFoundation底层偷偷操作了这个类、Runtime提前解析了这个类、提前调用了这个类的信息或类被提前修改了isa/方法/属性等。类一旦被提前操作,就不能正常懒加载了,必须马上强制初始化,否则会崩溃。

objc 复制代码
// dtrace probe
OBJC_RUNTIME_REALIZE_FUTURE_CLASSES_START();

// FutureClass:被提前碰过、被侵犯的类,可能是Runtime还没来得及初始化,CoreFoundation提前访问类结构
if (resolvedFutureClasses) {
    for (i = 0; i < resolvedFutureClassCount; i++) {
        Class cls = resolvedFutureClasses[i];
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class is not allowed to be future");
        }
        // Swift类不允许被提前侵犯,直接报错
        realizeClassWithoutSwift(cls, nil);
        // 强制把该类初始化完整
        cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
    }
    free(resolvedFutureClasses);
}

// dtrace probe
OBJC_RUNTIME_REALIZE_FUTURE_CLASSES_END();

分类如何加载到类

分类的加载并非完全独立,而是受non-lazy category机制统一约束。non-lazy category的触发条件可能只是某一个category需要加载期处理,但Runtime不能把同一镜像内的category拆开处理。一旦拆开处理,方法覆盖顺序、协议/属性附加顺序、+load收集顺序可能都不一致。因此只要一个category是non-lazy,该镜像内所有category都会被整体作为non-lazy category处理。

  • lazy category:懒加载分类。主类未实现时,存入unattachedCategories全局哈希表,等待主类realize后再合并。
  • non-lazy category:非懒加载分类。只要镜像内任意一个分类实现了+load,整个镜像所有分类都会被强制非懒加载,在镜像加载阶段就完成合并。

加载所有分类其实就是在分类中遍历,检查分类的主类是否已经实现。如果已经实现就将分类合并,否则就放到unattachedCategories中。这一步就是对之前提到的对懒加载分类的处理,当懒加载类实现之后,就将分类合并上去。

分类加载分两步:

  • 先在map_images的read_images里遍历Mach-O的__objc_catlist,把所有分类读进内存,主类没有准备好的放进unattachedCategories暂存。
  • 再在load_iamges里把分类合并到主类,并执行load。

+load执行

main函数之前。程序一启动就执行。

load_images

objc 复制代码
void
load_images(const struct _dyld_objc_notify_mapped_info* info)
{
    if (slowpath(PrintImages)) {
        _objc_inform("IMAGES: calling +load methods in %s\n", info->path ? info->path : "<null>");
    }


    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)info->mh, info->sectionLocationMetadata)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Load all pending categories if they haven't been loaded yet, and discover
    // load methods.
    {
        mutex_locker_t lock2(runtimeLock);
      // 加载所有未处理的分类。主类已实现,直接讲将分类合并到主类;主类未实现,存入unattachedCategories等待后续加载
        loadAllCategoriesIfNeeded();
      // 收集所有类、分类的+load方法
        prepare_load_methods((const headerType *)info->mh, info->sectionLocationMetadata);
    }

  // 统一执行所有+load方法
    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

prepare_load_methods

objc 复制代码
// 准备当前Mach-O镜像中的+load方法,这里只是收集加入待调用队列,没有真正调用
void prepare_load_methods(const headerType *mhdr, const _dyld_section_location_info_t info)
{
    size_t count, i;

    // 要求调用方已经持有runtimeLock。因为读取类列表、修改类内存是线程不安全的操作,Runtime强制要求必须持有锁,否则会发生多线程崩溃
    lockdebug::assert_locked(&runtimeLock.get());

    // 读取当前镜像中的non-lazy class list,对应Mach-O中的__objc_nlclslist
    // 本质是读取所有写了+load方法的类。因为普通类是懒加载的,用时才加载进内存,所以实现了+load方法的类是非懒加载类
    classref_t const *classlist = getSectionData<classref_t>(mhdr, info, _dyld_section_location_data_non_lazy_class_list, &count);
    // 遍历所有non-lazy class
    for (i = 0; i < count; i++) {
        // 把Mach-O里的旧类地址换成Runtime真正在用的新类,然后安排+load方法按顺序执行
        // remapClass方法:输入旧类指针,输出当前Runtime真正有效的新类指针
        // schedule_class_load方法:把类的+load方法加入待执行队列
        schedule_class_load(remapClass(classlist[i]));
    }

    // 读取non-lazy category list,存放实现了+load的分类
    category_t * const *categorylist = getSectionData<category_t *>(mhdr, info, _dyld_section_location_data_non_lazy_category_list, &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
        // SwiftStable ABI class不允许通过objc category/extension定义+load,如果发现,Runtime直接终止
        if (cls->isSwiftStable()) {
            _objc_fatal("Category %s on Swift class %s has +load method. Swift "
                        "class extensions and categories on Swift "
                        "classes are not allowed to have +load methods.",
                        cat->name, cls->nameForLogging());
        }
        // 在调度category的+load之前,确保category的目标类已完成realize
        realizeClassWithoutSwift(cls, nil);
        // 目标类的元类也必须已经realize
        ASSERT(cls->ISA()->isRealized());
        // 加入待调用列表
        add_category_to_loadable_list(cat);
    }
}

该方法将类及其分类的+load方法都加入到数组中,等待调用。

其中schedule_class_load方法:

objc 复制代码
static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    schedule_class_load(cls->getSuperclass());

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED);
}

根据类的继承链递归获取load方法,确保父类的load方法优先加载。

add_class_to_loadable_list

通过这个方法将load方法与cls类名一起添加到loadable_classes表中。

objc 复制代码
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    lockdebug::assert_locked(&loadMethodLock.get());

    // 只判断当前类
    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));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

getLoadMethod

+load方法是存在元类方法列表里,不存在类本身。类的+load一定在自己所有分类的+load之前

+load的逻辑应该是:realize主类->执行主类+load->合并分类到主类->执行分类+load

从元类中查找这个类自己实现的load方法,并返回它的IMP;反之没有发现,则返回nullptr。

objc 复制代码
NEVER_INLINE IMP
// 寻找这个类的+load方法,并返回其函数地址
objc_class::getLoadMethod()
{
    // 操作类结构必须加锁
    lockdebug::assert_locked(&runtimeLock.get());

    ASSERT(isRealized()); // 该类必须已经加载完成
    ASSERT(ISA()->isRealized()); // 元类也必须加载完成
    ASSERT(!isMetaClass()); // 自己不是元类,而是普通类
    ASSERT(ISA()->isMetaClass()); // 该类的ISA必须指向元类,还没realize的类、已经销毁的野指针的isa可能会不指向元类

    // 先找到元类,再找到元类的只读方法列表,再在元类的所有类方法里找+load
    auto &baseMethods = ISA()->data()->ro()->baseMethods;
    if (auto *list = baseMethods.dyn_cast<method_list_t *>()) {
        return _getLoadMethod(list);
    } else if (auto *listList = baseMethods.dyn_cast<relative_list_list_t<method_list_t> *>()) {
        // A load method will always be in the last list, since it's in the
        // class itself rather than in a category, and all other lists are
        // category lists.
        return _getLoadMethod(listList->lastList());
    }

    return nullptr;
}

add_category_to_loadable_list

同add_class_to_loadable_list,查找分类中load方法,将分类中load方法加入loadable_categories全局哈希表中。

objc 复制代码
void add_category_to_loadable_list(Category cat)
{
    IMP method;

    lockdebug::assert_locked(&loadMethodLock.get());

    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));
    }

    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

call_load_methods

执行时始终优先清空类的+load队列,再调用一轮分类+load,如果过程中又产生新的类或分类+load,就继续循环,直到没有待调用项。保证了+load的核心顺序:类优先分类,父类优先子类,动态加载中的类优先于后续分类

objc 复制代码
void call_load_methods(void)
{
    // 避免内层递归调用load,从而触发新的镜像加载
    static bool loading = NO;
    bool more_categories;

    lockdebug::assert_locked(&loadMethodLock.get());

    // 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;
}

+initialize分析

类第一次被使用时,自动调用一次+initialize。

realizeAndInitializeIfNeeded_locked

入口检查,先确保类加载完成,再执行initialize。

objc 复制代码
// 确保类已经realize+initialize,返回可用class
// p1:消息的接收者(实例/类对象) p2:要检查的类 p3:是否需要执行initialize
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
    lockdebug::assert_locked(&runtimeLock.get());
    // 确保类已经执行realize
    if (slowpath(!cls->isRealized())) {
        // 如果类还没实现,调用realize完成类结构构建,realizeClassMaybeSwiftAndLeaveLocked方法内部会暂时释放锁中,执行后重新加锁返回
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }

    // If the class was nilled out or disabled, we're done.
    if (!cls || !cls->ISA())
        return nil;

    // 确保类执行initialize
    if (slowpath(initialize && !cls->isInitialized())) {
        // 同上,类还未初始化执行initialize,内部同样暂时释放锁
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock may have been dropped but is now locked again

        // If sel == initialize, class_initialize will send +initialize and
        // then the messenger will send +initialize again after this
        // procedure finishes. Of course, if this is not being called
        // from the messenger then it won't happen. 2778172
    }
    // 返回加载完全、初始化完全、完全可用的类对象
    return cls;
}

initializeAndMaybeRelock

找到要初始化的那个类,确保它加载好,然后开始初始化。

objc 复制代码
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
    return initializeAndMaybeRelock(cls, obj, lock, true);
}
objc 复制代码
static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{
    lockdebug::assert_locked(&lock);

    cls->realizeIfNeeded_nolock();

    if (cls->isInitialized()) {
        if (!leaveLocked) lock.unlock();
        return cls;
    }

    // Find the non-meta class for cls, if it is not already one.
    // The +initialize message is sent to the non-meta class object.
    // 拿到非元类,initialize永远只发给普通类
    Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);

    // Realize the non-meta class if necessary.
    // 还没raelize就立即realize
    if (nonmeta->isRealized()) {
        // nonmeta is cls, which was already realized
        // OR nonmeta is distinct, but is already realized
        // - nothing else to do
        lock.unlock();
    } else {
        nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
        // runtimeLock is now unlocked
        // fixme Swift can't relocate the class today,
        // but someday it will:
        cls = object_getClass(nonmeta);
    }

    // runtimeLock is now unlocked, for +initialize dispatch
    ASSERT(nonmeta->isRealized());
    // 真正调用initialize
    initializeNonMetaClass(nonmeta);

    if (leaveLocked) runtimeLock.lock();
    return cls;
}

initializeNonMetaClass

真正执行+initialize的地方。

objc 复制代码
void initializeNonMetaClass(Class cls)
{
    ASSERT(!cls->isMetaClass());

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    // 1.先初始化父类,递归保证父类优先,父类永远比子类先执行initialize
    Class supercls = cls->getSuperclass();
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }

    // Acquire the initialization lock for this class.
    // 2.加锁,防止多线程同时初始化同一个类
    lockClass(cls);

    // Now that it's acquired, there are three possibilities:
    // 1. Initialized. We waited, now it's done, return.
    // 2. Initializing.
    //    A. This thread is already initializing the class and we
    //       reacquired the recursive lock.
    //    B. We're in the child of a fork, another thread was initializing the
    //       class in the parent process, and no longer exists in the child.
    // 3. Neither. This thread won the race to initialize cls, do it.
    // 加锁后,出现3种情况:1.类已经初始化完成,直接返回 2.类正在初始化中,处理重入、fork异常 3.未初始化,则在当前线程执行初始化

    // Case 1, we waited.
    if (cls->isInitialized()) {
        unlockClass(cls);
        return;
    }

    // Case 2, we reentered initialization.
    if (cls->isInitializing()) {
        // Case 2A, we're not in a fork child, or we are but the class is
        // initializing on this thread, so we can just return.
        // 当前线程就是正在执行初始化该类的线程,直接返回,不重复执行。例如:initialize内部又发消息给自己,导致递归触发初始化
        if (!MultithreadedForkChild || _thisThreadIsInitializingClass(cls)) {
            unlockClass(cls);
            return;
        } else {
            // fork子进程异常:父进程在初始化时调用fork()创建子线程,子线程只保留一个线程,原初始化线程消失。类卡在正在初始化状态,需要子线程接管修复
            // Case 2B, we're on the child side of fork(), facing a class that
            // was initializing by some other thread when fork() was called.
            // The lock for this class has been dropped, so reacquire it here.
            
            lockClass(cls); // 重新加锁,接管初始化
            _setThisThreadIsInitializingClass(cls); // 标记当前子线程负责初始化
            performForkChildInitialize(cls, supercls); // 执行子进程专用安全初始化逻辑
        }
    }

    // Case 3, we won the race. Set CLS_INITIALIZING and gather will-initialize
    // functions.
    SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
    {
        mutex_locker_t lock(classInitLock);
        // 标记类初始化中
        cls->setInitializing();

        // Runtime注册一些类即将初始化的回调函数,这里拷贝一份到本地,避免执行回调时长期持有全局锁
        localWillInitializeFuncs.initFrom(willInitializeFuncs);
    }
    
    // Record that we're initializing this class so we can message it.
    // 记录当前线程正在初始化这个类,避免重入
    _setThisThreadIsInitializingClass(cls);

    // fork的子线程不执行initialize,直接走特殊逻辑
    if (MultithreadedForkChild) {
        // LOL JK we don't really call +initialize methods after fork().
        performForkChildInitialize(cls, supercls);
        return;
    }

    // 类初始化前的全局通知钩子,回调监听函数
    for (auto callback : localWillInitializeFuncs)
        callback.f(callback.context, cls);

    // Send the +initialize message.
    // Note that +initialize is sent to the superclass (again) if
    // this class doesn't implement +initialize. 2157218
    // 真正调用initialize方法
    if (PrintInitializing) {
        _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
                     objc_thread_self(), cls->nameForLogging());
    }

    // Exceptions: A +initialize call that throws an exception
    // is deemed to be a complete and successful +initialize.
    // 异常保护:无论initialize是否抛出异常,都会标记类初始化完成
    @try
    {
        callInitialize(cls);

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                         objc_thread_self(), cls->nameForLogging());
        }
    }
    @catch (...) {
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                         "threw an exception",
                         objc_thread_self(), cls->nameForLogging());
        }
        @throw;
    }
    @finally
    {
        // Done initializing.
        lockAndFinishInitializing(cls, supercls);
    }
}

这里值得注意的是:

因为initialize走正常objc_msgSend消息查找流程,因此:

  • 在类或它的任何子类第一次被调用方法前自动执行一次。
  • 父类先执行initialize,子类后执行。
  • 子类没实现的情况:
    • 子类不写initialize:自动调用父类的initialize
    • 子类写initialize:只执行子类的initialize,不执行父类的
  • 主类、分类都实现initialize,只执行分类的;多个分类都实现,只执行最后编译加载的那个分类的initialize。

总结

initialize和load的区别:

  • initialize在第一次使用类时调用;load在main函数前,启动时调用。
  • initialize走消息发送流程,会覆盖;load直接调用,不覆盖。
  • 子类不实现时:initialize自动调用父类的;load直接不执行load方法。
  • 分类实现时:initialize覆盖主类;load主类、分类都执行。
相关推荐
Engineer邓祥浩10 小时前
宏观认知(1):AI 是什么——吴恩达《AI for Everyone》Week1 学习笔记
人工智能·笔记·学习
暴躁小师兄数据学院10 小时前
【AI大模型应用开发工程师特训笔记】第04讲(第五章):条件判断与流程控制
大数据·人工智能·python·学习
ayqy贾杰10 小时前
我同事,40了,他vibe coding了个App
前端·ios·客户端
3DVisionary10 小时前
混凝土裂纹如何全自动识别?DIC技术在结构裂缝重构的应用
人工智能·学习·dic技术·混凝土裂缝监测·全场应变分析·三维位移测量·实验力学
魔法阵维护师11 小时前
从零开发游戏需要学习的c#模块,第二十八章(血条显示 —— 敌人与玩家生命可视化)
学习·游戏·c#
AOwhisky11 小时前
Ceph系列第一期:Ceph分布式存储核心概念与架构初识
linux·运维·笔记·分布式·ceph·学习·架构
Bechamz11 小时前
大数据开发学习Day44
大数据·学习
锦鲤521411 小时前
深度学习与神经网络学习
深度学习·神经网络·学习