【iOS】类和分类的加载

_objc_init源码

这个liobjc是iOS中的一个底层动态库,他是在libobjc.A.dylib被加载时由系统启动流程间触发的。

  • 进程启动
  • dyld 开始装载主程序和依赖动态库
  • libSystem 初始化
  • libSystem 很早就调用 _objc_init()
  • _objc_init() 里把 ObjC runtime 先启动起来,并向 dyld 注册 image 回调
  • 之后dyld 再继续走库初始化、静态构造器、image 通知、+load 等后续流程

可以看到这个方法中进行了许多初始化函数

environ_init

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, "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;
                }
            }
        }
    }
}

这段代码主是进行了一些环境变量的配置。

  • DYLD_PRINT_STATISTICS:设置 DYLD_PRINT_STATISTICSYES,控制台就会打印 App 的加载时长,包括整体加载时长和动态库加载时长,即main函数之前的启动时间(查看pre-main耗时),可以通过设置了解其耗时部分,并对其进行启动优化
  • OBJC_DISABLE_NONPOINTER_ISA:禁用生成相应的nonpointer isa(nonpointer isa指针地址 末尾为1 ),生成的都是普通的isa
  • OBJC_PRINT_LOAD_METHODS:打印 ClassCategory+ (void)load 方法的调用信息
  • NSDoubleLocalizedStrings:项目做国际化本地化(Localized)的时候是一个挺耗时的工作,想要检测国际化翻译好的语言文字UI会变成什么样子,可以指定这个启动项。可以设置 NSDoubleLocalizedStringsYES
  • NSShowNonLocalizedStrings:在完成国际化的时候,偶尔会有一些字符串没有做本地化,这时就可以设置NSShowNonLocalizedStringsYES,所有没有被本地化的字符串全都会变成大写

配置OBJC_PRINT_LOAD_METHODS可以监控所有的load方法从而处理启动优化

tis_init

这个方法可能是因为版本原因,上面没有显示,实际上就是对本地线程池的一个初始化。

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

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]);
        init();
    }
#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.");
#endif
}

在dylb调用静态构造函数之前,libc会自动调用objc_init,导致其中的一些C++静态构造函数需要我们自己去实现。主要运行的是运行系统级别的C++静态静态构造函数,系统级别的C++构造函数先与自定义的C++构造函数

runtime_init

运行时初始化,主要是类的初始化和类的表初始化

objc 复制代码
void runtime_init(void)
{
    objc::Scanner::init();
    objc::disableEnforceClassRXPtrAuth = DisableClassRXSigningEnforcement;//安全配置
    objc::unattachedCategories.init(32);//初始化一个全局表,存储待附加分类
    objc::allocatedClasses.init();//把"动态分配类集合"先建好,后面给 objc_allocateClassPair、类合法性检查、类销毁流程使用。
}

exception_init

主要是初始化libobjc的异常处理系统,注册异常处理的回调,从而监控异常的处理

objc 复制代码
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

当有crash发生时,会来到_objc_terminate方法,最后走到uncaught_handler抛出异常

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.
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // It's not an objc object. Continue to C++ terminate.
            (*old_terminate)();
        }
    }
}

在app层会传入一个函数用于处理异常,便于调用函数,然后回到原有的app层中

objc 复制代码
objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}
  • Mach异常(内核层)
  1. Unix信号(系统层)//主要的crash在这个层级
  2. NSException(应用层)//未捕获的OC"异常,runtime会调用abort发送SIGABRT信号造成crash

针对应用级异常,可以通过注册异常捕获函数,即NSSetUncaughtExceptionHandler机制实现线程的保活,并收集上床崩溃日志

在实际开发中可以针对Crash进行一个拦截处理,即app代码中给出一个异常句柄NSSetUncaughtExceptionHandler,传入一个函数给系统,当异常发生后,调用函数(函数中可以线程保活、收集并上传崩溃日志),然后回到原有的app层中,其本质就是一个回调函数。

cache_init

初始化缓存。具体代码过长,这里就不粘出来了。

_imp_implementationWithBlock_init

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

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)) {
        Trampolines.Initialize();//初始化trampoline子系统,提前加载libobjc-trampolines.dylib
    }
#endif
}

_dyld_objc_notify_register

注册dyld回调函数,仅供objc运行时使用,注册处理程序,便于在映射、取消映射和初始化dyld图像时调用。

dyld将通过一个包含objc-image-info的镜像文件的数组回调mapped函数。

三个参数:

  • map_images:dyld将image镜像文件加载进入内存中,会触发该函数
  • Laod-image:dyld初始化image时会调用
  • Unman_image:dyld移除image时触发该函数
objc 复制代码
_dyld_objc_callbacks_v2 callbacks = {
        2, // version
        &map_images,
        &load_images,
        unmap_image,
        _objc_patch_root_of_class
    };
    _dyld_objc_register_callbacks((_dyld_objc_callbacks*)&callbacks);

总结

App 启动时,内核先加载 dyld,dyld 负责加载主程序和依赖动态库。dyld 在初始化过程中会执行各个 image 的 initializer,其中 libSystem 的初始化会触发 libdispatch 和 libobjc 的初始化。libobjc 中的 _objc_init 会调用 _dyld_objc_notify_register,把 map_images、load_images、unmap_image 三个回调注册给 dyld。之后 dyld 在加载 Mach-O image 时,会通过这些回调通知 Objective-C Runtime。map_images 负责读取 Mach-O 中的 ObjC 元数据,例如类、分类、协议,并完成注册;load_images 负责调用类和分类的 +load 方法;unmap_image 则用于 image 卸载时清理。所有这些工作完成后,dyld 才会把控制权交给 main 函数。

类的加载流程

  • map_images引用类型,外界变了,跟着变。
  • load_images值类型,不传递值

map_images:加载镜像文件到内存

主要作用是将Mach-O中的类信息加载到内存中。当OC的runtime被dyld通知有新的Mach-O镜像被映射进进程时调用入口函数。处理刚刚加载进来的镜像,并将其中的OC元数据注册进runtime

objc 复制代码
void
map_images(unsigned count, const struct _dyld_objc_notify_mapped_info infos[])
{
    bool takeEnforcementDisableFault;

    {
        mutex_locker_t lock(runtimeLock);
        map_images_nolock(count, infos, &takeEnforcementDisableFault);
    }

    if (takeEnforcementDisableFault) {
        if (DebugClassRXSigning == Fatal)
            _objc_fatal("class_rx signing mismatch");

#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
        bool objcModeNoFaults = DisableFaults
            || DisableClassROFaults
            || getpid() == 1
            || is_root_ramdisk()
            || !os_variant_has_internal_diagnostics("com.apple.obj-c");
        if (!objcModeNoFaults) {
            os_fault_with_payload(OS_REASON_LIBSYSTEM,
                                  OS_REASON_LIBSYSTEM_CODE_FAULT,
                                  NULL, 0,
                                  "class_ro_t enforcement disabled",
                                  0);
        }
#endif
    }
}

我们进入map_images_nolock,只保留关键部分如下:

objc 复制代码
void map_images_nolock(unsigned mhCount,
                       const struct _dyld_objc_notify_mapped_info infos[],
                       bool *disabledClassROEnforcement)
{
    static bool firstTime = YES;//判断当前进程是不是第一次执行map_images_nolock,因为OC的初始化不是一开始就全部完成,而是在dyld加载镜像时逐步触发,第一次进来的时候会有一些全局初始化需要实现
    static bool executableHasClassROSigning = false;//是否启动class_ro_t指针签名
    static bool executableIsARM64e = false;

    mapped_image_info mappedInfos[mhCount];
    uint32_t hCount = 0;
    size_t selrefCount = 0;

    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;//没有被预优化的类数量,提前处理了一部分Objc元数据的类

    *disabledClassROEnforcement = false;//默认没有关闭

    // 1. 首次初始化或补充加载分类
    if (firstTime) {
        preopt_init();//初始化OC得runtime使用dyld预优化数据的环境**
    } else {
        loadAllCategoriesIfNeeded();//处理一个category的特殊补救逻辑**
    }

    // 2. 扫描所有新映射镜像,找出包含 Objective-C 元数据的镜像
    for (uint32_t i = 0; i < mhCount; i++) {
        const headerType *mhdr = (const headerType *)infos[i].mh;

        auto hi = addHeader(mhdr,
                            infos[i].path,
                            infos[i].sectionLocationMetadata,
                            totalClasses,
                            unoptimizedTotalClasses);//检查类中的元数据

        if (!hi) {//如果没有直接提条福哦当前镜像
            continue;
        }

        mapped_image_info mappedInfo{hi, infos[i]};//组成一个完整镜像

        // 3. 处理主可执行文件相关信息
        if (mhdr->filetype == MH_EXECUTE) {
            if (mappedInfo.dyldObjCRefsOptimized()) {//是否优化引用
                size_t count = 0;

                hi->selrefs(&count);
                selrefCount += count;

                hi->messagerefs(&count);
                selrefCount += count;
            }

            if (hasSignedClassROPointers(hi)) {
                executableHasClassROSigning = true;
            }
        }

        mappedInfos[hCount++] = mappedInfo;

        // dtrace probe
        OBJC_RUNTIME_LOAD_IMAGE(hi->fname(),
                                mhdr->filetype == MH_BUNDLE,
                                hi->info()->hasCategoryClassProperties(),
                                hi->info()->optimizedByDyld());//观察runtime加载行为
    }

    // 4. 首次运行时初始化
    if (firstTime) {
        sel_init(selrefCount);//初始化selector系统
        arr_init();//初始化自动释放池、引用管理或者相关runtime基础设施有关的结构

        const headerType *mainExecutableHeader =
            (headerType *)_dyld_get_prog_image_header();

        if (mainExecutableHeader &&
            mainExecutableHeader->cputype == CPU_TYPE_ARM64 &&
            ((mainExecutableHeader->cpusubtype & ~CPU_SUBTYPE_MASK)
                == CPU_SUBTYPE_ARM64E)) {
            executableIsARM64e = true;
        }
    }

    // 5. ARM64e 下检查 class_ro_t 指针签名
    if (executableIsARM64e) {
        bool shouldWarn = executableHasClassROSigning && DebugClassRXSigning;

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = mappedInfos[i].hi;

            if (!hasSignedClassROPointers(hi)) {
                if (!objc::disableEnforceClassRXPtrAuth) {
                    *disabledClassROEnforcement = true;
                    objc::disableEnforceClassRXPtrAuth = 1;
                }

                if (shouldWarn) {
                    _objc_inform("%s has un-signed class_ro_t pointers",
                                 hi->fname());
                }
            }
        }
    }

    // 6. 读取 Objective-C 镜像元数据
    if (hCount > 0) {
        _read_images(mappedInfos,
                     hCount,
                     totalClasses,
                     unoptimizedTotalClasses);//正式解析,处理类、分类、协议等信息的注册
    }

    firstTime = NO;

    // 7. 执行镜像加载回调
    for (auto callback : loadImageCallbacks) {
        for (uint32_t i = 0; i < mhCount; i++) {
            switch (callback.kind) {
                case 1:
                    callback.func(infos[i].mh);
                    break;

                case 2:
                    callback.func2(infos[i].mh,
                                   infos[i].sectionLocationMetadata);
                    break;

                default:
                    _objc_fatal("Corrupt load image callback");
            }
        }
    }
}

我们抽象一下流程,大概如下:

objc 复制代码
map_images_nolock()
{
    if (第一次进入)
        初始化 dyld 预优化信息;
    else
        补充加载 category;

    for (每个 dyld 新映射镜像) {
        读取 Mach-O header;

        if (没有 Objective-C 元数据)
            跳过;

        记录 ObjC 镜像信息;

        if (这是主可执行文件) {
            统计 selector/message refs;
            检查 class_ro_t 是否启用签名;
        }

        触发 runtime load image probe;
    }

    if (第一次进入) {
        初始化 selector 表;
        初始化 runtime 基础结构;
        判断主程序是否为 ARM64e;
    }

    if (主程序是 ARM64e) {
        检查所有 ObjC 镜像的 class_ro_t 指针签名;

        if (发现未签名镜像) {
            关闭 enforcement;
            通知外层可能需要 fault;
        }
    }

    if (存在 ObjC 镜像) {
        正式读取并注册 ObjC 元数据;
    }

    标记不再是第一次;

    执行镜像加载回调;
}

_read_images主要的就是加载类信息,主要分为以下几部分:

  1. 条件控制进行的一次加载
  2. 修复预编译阶段的@selector混乱问题
  3. 错误混乱的类处理
  4. 修复重映射一些没有被镜像文件加载进来的类
  5. 修复一些消息
  6. 当类中有协议时,readProtocal读取协议
  7. 修复没有被加载的协议
  8. 分类处理
  9. 类的加载处理
  10. 没有被处理的类,优化那些被侵犯的类

1.创建表

objc 复制代码
if (!doneOnce) {
        // dtrace probe
        OBJC_RUNTIME_FIRST_TIME_START();

        doneOnce = YES;
        launchTime = YES;

#if SUPPORT_NONPOINTER_ISA
        // Disable non-pointer isa under some conditions.

# if SUPPORT_INDEXED_ISA
        // Disable nonpointer isa if any image contains old Swift code
        for (auto info : infos) {
            if (info.hi->info()->containsSwift()  &&
                info.hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
            {
                DisableNonpointerIsa = On;
                if (PrintRawIsa) {
                    _objc_inform("RAW ISA: disabling non-pointer isa because "
                                 "the app or a framework contains Swift code "
                                 "older than Swift 3.0");
                }
                break;
            }
        }
# endif

# if TARGET_OS_OSX
#   if !TARGET_OS_EXCLAVEKIT
        // Disable non-pointer isa if the app is too old
        // (linked before OS X 10.11)
        // Note: we must check for macOS, because Catalyst and Almond apps
        // return false for a Mac SDK check! rdar://78225780
        if (dyld_get_active_platform() == PLATFORM_MACOS) {
            DisableNonpointerIsa = On;
            if (PrintRawIsa) {
                _objc_inform("RAW ISA: disabling non-pointer isa because "
                             "the app is too old.");
            }
        }
#   endif

        // Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
        // New apps that load old extensions may need this.
        for (auto info : infos) {
            if (info.hi->mhdr()->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (info.hi->hasRawISASection()) {
                DisableNonpointerIsa = On;
                if (PrintRawIsa) {
                    _objc_inform("RAW ISA: disabling non-pointer isa because "
                                 "the app has a __DATA,__objc_rawisa section");
                }
            }
            break;  // assume only one MH_EXECUTE image
        }
# endif

#endif

        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }

        initializeTaggedPointerObfuscator();

        if (PrintConnecting) {
            _objc_inform("CLASS: found %d classes during launch", totalClasses);
        }

        // namedClasses
        // Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        int namedClassesSize =
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        NXMapTablePrototype namedClassesPrototype = NXStrValueMapPrototype;
#if __has_feature(ptrauth_calls)
        // Only set this when we have ptrauth, we use the standard callback
        // otherwise.
        namedClassesPrototype.hash = namedClassTableHashCallback;
#endif
        gdb_objc_realized_classes =
            NXCreateMapTable(namedClassesPrototype, namedClassesSize);

        // dtrace probe
        OBJC_RUNTIME_FIRST_TIME_END();
    }

这段代码的主要作用就是为运行时的类名查找表gdb_objc_realized_classes 分配并初始化 NXMapTable,用于保存非预优化的已实现类的类名到类对象映射,表大小按类数量和负载因子预估,在支持指针认证的平台上还会替换专用的哈希回调。(被dyld预优化过的类不会被处理)

2.修复预编译阶段的@selector混乱问题

objc 复制代码
  static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (auto info : infos) {
            if (info.dyldObjCRefsOptimized()) continue;

            bool isBundle = info.hi->isBundle();
            SEL *sels = info.hi->selrefs(&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) {//值相同,但是地址可能不相同,需要fix一下
                    sels[i] = sel;
                }
            }
        }
    }

主要是在加载镜像时对未被dyld预优化的selector引用进行修正和唯一化注册。把各个镜像中零散的 selector 引用统一修正为 Runtime 全局唯一的 selector 指针,保证同名方法选择子在整个进程内只有一个标准 SEL 表示,并为后续消息发送、方法查找和比较提供一致性。

3.错误混乱的类处理

objc 复制代码
// 判断 dyld shared cache 中是否有镜像被外部镜像覆盖。
// 如果存在 override,某些 dyld 预优化结果可能不能完全信任,
// 后面判断是否必须重新读取类时会用到这个信息。
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();


// 遍历当前这批待处理的 Mach-O 镜像。
// 每个 info 表示一个镜像的 Objective-C 元数据信息。
for (auto info : infos) {

    // 判断当前镜像是否必须由 Objective-C Runtime 重新读取类信息。
    //
    // 如果返回 false,说明这个镜像已经被 dyld 充分优化,
    // Runtime 不需要逐个调用 readClass() 注册类。
    //
    // 典型情况:系统库、shared cache 中已经预优化过的镜像。
    if (! mustReadClasses(info, hasDyldRoots)) {

        // 当前镜像优化程度足够高,不需要调用 readClass()。
        // 直接跳过这个镜像,处理下一个。
        continue;
    }


    // 从当前镜像的 __objc_classlist 中取出类列表。
    //
    // classlist 指向该镜像中定义的所有 Objective-C 类。
    // count 会被设置为类的数量。
    classref_t const *classlist = info.hi->classlist(&count);


    // 判断当前镜像是否是 bundle。
    //
    // bundle 通常是运行时动态加载的模块,例如插件、测试 bundle 等。
    // readClass() 会根据这个信息采用不同的处理策略。
    bool headerIsBundle = info.hi->isBundle();


    // 判断当前镜像的 Objective-C 引用是否已经被 dyld 预优化。
    //
    // 如果为 true,说明 selector refs、class refs、super refs 等
    // 可能已经被 dyld 提前修正过。
    bool headerIsPreoptimized = info.dyldObjCRefsOptimized();


    // 遍历当前镜像中的每一个类。
    for (i = 0; i < count; i++) {

        // 取出类列表中的原始 Class 指针。
        //
        // 这个 cls 来自 Mach-O 的 __objc_classlist,
        // 是编译器/链接器生成的类结构。
        Class cls = (Class)classlist[i];


        // 读取并注册这个类。
        //
        // readClass() 会把 Mach-O 中的类结构纳入 Runtime 管理,
        // 包括类名登记、父类关系处理、metaclass 处理、
        // future class 解析、状态标记等。
        //
        // 普通情况下,返回值 newCls 等于 cls。
        // 特殊情况下,例如解析 future class,返回值可能是另一个 Class。
        Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);


        // 如果 readClass() 返回的类和原始 cls 不同,并且 newCls 非空,
        // 说明这个类没有被删除,而是被"移动"到了另一个 Class 结构上。
        //
        // 当前 Runtime 中,这种情况主要发生在:
        // 新加载的真实类解析了之前创建的 future class。
        if (newCls != cls  &&  newCls) {

            // 类被移动但没有被删除。
            // 当前主要场景是 newCls 解析了一个 future class。
            //
            // 这种类后面需要立即 realize,不能再懒加载。
            // 因此先把它记录到 resolvedFutureClasses 数组中。

            // 扩容 resolvedFutureClasses 数组,
            // 新大小为 resolvedFutureClassCount + 1 个 Class。
            resolvedFutureClasses = (Class *)
                realloc(resolvedFutureClasses,
                        (resolvedFutureClassCount+1) * sizeof(Class));


            // 把解析后的 future class 保存起来,
            // 并递增 resolvedFutureClassCount。
            resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
        }
    }
}

4.修复重映射一些没有被镜像文件加载进来的类

objc 复制代码
if (!noClassesRemapped()) {
        for (auto info : infos) {
            Class *classrefs = info.hi->classrefs(&count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);//上面说过的future Class的例子,就需要remap
            }
            // 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]);
            }
        }
    }

如果runtime发现有Class指针发生过重映射,就遍历所有镜像中的类引用和父类引用,将旧的Class指针修正成新的Class指针

  • _getObjc2ClassRefs是获取Mach-O中的静态段、__objc_classrefs类的引用
  • _getObjc2SuperRefs是获取Mach-O中的静态段__objc_superrefs父类的引用

remap必须发生在类真正realize之前,而绝大多数还没realize的类正好属于懒加载类

5.修复一些消息

objc 复制代码
 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);
        }
    }

遍历当前加载的所有 Mach-O 镜像,找到其中的旧式 Objective-C 消息发送引用 messagerefs,并逐个调用 fixupMessageRef() 进行修复,使这些旧的消息发送调用点能够在当前 Runtime 下正常工作。

6.读取协议列表

objc 复制代码
for (auto info : infos) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        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();

        protocol_t * const *protolist = info.hi->protocollist(&count);//获取镜像文件中的静态段协议列表,即从编译器中读取并初始化protocal
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map,
                         isPreoptimized, isBundle);
        }
    }

遍历当前加载的 Mach-O 镜像,读取其中定义的 Objective-C 协议 protocol,并把这些协议注册/合并到 Runtime 的全局协议表中;但在启动阶段,如果某个镜像已经被 dyld 预优化,则可以跳过读取,以减少启动开销。

7.修复没有被加载的协议

objc 复制代码
 for (auto info : infos) {
        // 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.
        if (launchTime && info.hi->isPreoptimized())
            continue;
        protocol_t **protolist = info.hi->protocolrefs(&count);//镜像文件中的协议引用段
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
    }

为什么被dyld预优化过的

镜像可以跳过?启动阶段,预优化镜像里的协议引用已经被 dyld 修好了,指向 shared cache 里的协议定义,所以 Runtime 不需要再检查。

objc 复制代码
static size_t UnfixedProtocolReferences;//计数修正数量
static void remapProtocolRef(protocol_t **protoref)
{
    lockdebug::assert_locked(&runtimeLock);

    protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);
    if (*protoref != newproto) {
        *protoref = newproto;
        UnfixedProtocolReferences++;
    }
}

8.分类处理

objc 复制代码
 if (didInitialAttachCategories) {
        for (auto info : infos) {
            load_categories_nolock(info.hi);
        }
    }

这里主要是处理分类,需要在分类初始化并将数据加载到类之后才执行,对与运行时出现的分类,将分类的发现推迟到_dyld_objc_notify_register的调用完成后的第一个load_images调用为止

9.类的加载处理

objc 复制代码
// 遍历当前这一批被 dyld 加载并交给 ObjC Runtime 处理的镜像。
// 每个 info 表示一个 Mach-O image。
for (auto info : infos) {

    // 取出当前镜像中的 non-lazy class list。
    //
    // nlclslist 对应 Mach-O 中的 __objc_nlclslist。
    // 里面存放的是必须在镜像加载阶段立即 realize 的类。
    //
    // 常见情况:实现了 +load 方法的类。
    classref_t const *classlist = info.hi->nlclslist(&count);

    // 遍历当前镜像中的所有 non-lazy class。
    for (i = 0; i < count; i++) {

        // classlist / nlclslist 本身可能没有被直接 remap。
        //
        // 如果前面 readClass() 阶段发生了 Class remap,
        // 例如 oldCls -> newCls,
        // 那么这里必须先通过 remapClass() 取得 Runtime 当前认可的 Class。
        Class cls = remapClass(classlist[i]);

        // 如果 remap 后没有有效 Class,
        // 说明该类无效、缺失或已被 Runtime 忽略,直接跳过。
        if (!cls) continue;

        // 将该类加入 Runtime 的类表。
        //
        // 这样 objc_getClass()、NSClassFromString() 等运行时查找
        // 能够找到这个类。
        //
        // non-lazy class 后续马上可能被 realize 和调用 +load,
        // 所以需要确保它已经在类表中可见。
        addClassTableEntry(cls);

        // 如果这是一个 Swift stable ABI 类,
        // 需要额外检查它是否带 Swift metadata initializer。
        if (cls->isSwiftStable()) {

            // 带 Swift metadata initializer 的 Swift 类
            // 不能作为 ObjC non-lazy class 在这里直接 realize。
            //
            // 因为这种类的元数据初始化需要 Swift runtime 参与,
            // 而 realizeClassWithoutSwift() 不负责执行 Swift metadata initializer。
            if (cls->swiftMetadataInitializer()) {
                _objc_fatal("Swift class %s with a metadata initializer "
                            "is not allowed to be non-lazy",
                            cls->nameForLogging());
            }

            // FIXME:
            // 理论上还应该禁止 relocatable Swift classes。
            //
            // 但不能禁止所有 Swift 类,
            // 因为 Swift 标准库中存在一些可以走该路径的特殊类,
            // 例如 Swift.__EmptyArrayStorage。
            // fixme also disallow relocatable classes
            // We can't disallow all Swift classes because of
            // classes like Swift.__EmptyArrayStorage
        }

        // 立即 realize 这个 non-lazy class。
        //
        // readClass() 只是把类读入 Runtime;
        // realizeClassWithoutSwift() 会真正展开类结构:
        // - 建立 superclass / metaclass 关系
        // - 初始化 class_rw_t
        // - 整理 method / property / protocol list
        // - 初始化 cache
        // - 标记类为 realized
        //
        // 完成后,该类可以参与消息发送、方法查找和 +load 调用。
        realizeClassWithoutSwift(cls, nil);
    }
}

这段代码负责将所有的非懒加载类立即加入runtime类表并完成realize,使他们在+load或者后续使用前已经与欧完整的运行时结构。(如果一个类实现了load方法,那么它通常会进入非懒加载类,以为+load要在镜像加载时由runtime主动调用)

10.没有被处理的类,优化那些被侵犯的类

objc 复制代码
// 如果前面 readClass() 阶段解析过 future class,
// resolvedFutureClasses 会保存这些已经解析成功的 future Class。
if (resolvedFutureClasses) {

    // 遍历所有本轮解析成功的 future class。
    for (i = 0; i < resolvedFutureClassCount; i++) {

        // 取出一个已经解析成功的 future class。
        // 这个 cls 通常不是 Mach-O classlist 里的原始 Class,
        // 而是 readClass() 返回的 newCls。
        Class cls = resolvedFutureClasses[i];

        // Swift stable ABI class 不允许使用 ObjC future class 机制。
        // 因为 Swift class 的元数据初始化由 Swift runtime 管理,
        // 不能简单用 Objective-C Runtime 的 future Class 占位承接。
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class is not allowed to be future");
        }

        // 立即 realize 这个 future class。
        //
        // 普通 lazy class 可以等第一次使用时再 realize;
        // 但 future class 的 Class 指针可能已经提前暴露给外部代码,
        // 所以真实类解析完成后必须立即完成 Runtime 展开。
        //
        // realize 后,该类会完成 superclass/metaclass 连接、
        // class_rw_t 初始化、方法/协议/属性列表整理、cache 初始化等。
        realizeClassWithoutSwift(cls, nil);

        // future class 在解析前可能被 Runtime 保守地要求实例使用 raw isa。
        //
        // 现在真实类已经解析并 realize 完成,
        // 可以取消"强制 raw isa"的限制,
        // 让该类及相关子类按正常规则使用 isa 策略。
        cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
    }

    // 释放临时数组。
    //
    // 注意:这里只释放 resolvedFutureClasses 数组本身,
    // 不释放数组中的 Class 对象。
    free(resolvedFutureClasses);
}

readClass

读取类,在未调用该方法之前,cls只是一个地址,执行该方法之后,cls是类的名称。这个函数完成的是类发现阶段的注册、future class 解析、弱链接父类剔除、预优化路径区分以及 bundle 来源标记 ,但它还不是完整的类展开,真正的方法列表、父类链、缓存等运行时结构初始化会在后续 realizeClass...阶段完成

realizeClassWithoutSwift

实现将类的data数据加载到内存中,主要有以下几步:

  1. 读取data数据,并设置ro、rw:这一步读取class的data数据,并将其强制转换为ro,并初始化rw,将ro中的数据拷贝一份到rw的ro中。(rw具有动态性,但是实际上之后10%左右的类真正更改了他们的方法,所以衍生出了rwe即类的额外信息,对那些需要额外信息的类分配一份rwe并将其放入类中供其使用。)
  2. 递归调用realizeClassWithoutSwift完善继承链:递归调用 realizeClassWithoutSwift 设置父类、元类。设置父类和元类的isa指向 。通过addSubclassaddRootClass 设置父子的双向链表指向关系 ,即父类中可以找到子类,子类中可以找到父类。在realize~递归调用时,isa找到根元类后,根元类的isa指向自己,并不会返回nil,所以需要有下面几个终止条件:1.如果类不存在返回nil。2.如果类已经实现返回nil
  3. 通过methodizeClass方法化类

methodizeClass

该方法会提取类本身的方法、属性、协议,存入rw的动态列表,从运行时获取的分类的方法、属性、协议合并到rw对应表中,确保rw包含所有可访问的成员,供运行时的动态查询

方法列表加入rwe的逻辑

  • 取出类自己在编译器的方法列表(只读)
  • 对方法列表做运行时预处理
  • 将方法列表挂入class_rw_ext_t 的 methods 中

attachToClass

将分类添加到主类中。

attachCategories

注意点

  • 逆序存储正序附加:通过倒序填充缓冲区,正序附加,确保后加载的分类内容优先生效。
  • 分批处理:每满64个分类处理一次,平衡性能与内存占用。
  • 方法覆盖:分类方法插入到类方法列表头部,实现"后编译的分类覆盖先编译的类或分类方法"。

分类的加载时机

non-lazy category 的触发条件可能只是某一个 category 需要在加载期处理,但 Runtime 不能把同一镜像内的 category 拆开处理;一旦拆开,方法覆盖顺序、协议/属性附加顺序、+load 收集顺序都可能不一致。因此只要一个 category 是 non-lazy,该镜像内所有 category 都会被整体作为 non-lazy categories 处理。

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);
        loadAllCategoriesIfNeeded();
        prepare_load_methods((const headerType *)info->mh, info->sectionLocationMetadata);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

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

prepare_load_methods

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

    // 要求调用方已经持有 runtimeLock。
    // 因为这里会访问类状态、remap 表和 +load 待调用列表。
    lockdebug::assert_locked(&runtimeLock);

    // 读取当前镜像中的 non-lazy class list。
    //
    // 对应 Mach-O 中的 __objc_nlclslist。
    // 这里通常存放实现了 +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++) {

        // classlist[i] 可能是旧 Class 指针。
        // 前面的 readClass() 可能发生过 class remap:
        // oldCls -> newCls 或 oldCls -> nil。
        //
        // 所以这里先 remapClass(),拿到 Runtime 当前认可的 Class。
        //
        // schedule_class_load() 会把类的 +load 安排到待调用列表中,
        // 并保证父类 +load 先于子类 +load。
        schedule_class_load(remapClass(classlist[i]));
    }

    // 读取当前镜像中的 non-lazy category list。
    //
    // 对应 Mach-O 中的 __objc_nlcatlist。
    // 这里通常存放实现了 +load 的分类。
    category_t * const *categorylist =
        getSectionData<category_t *>(
            mhdr,
            info,
            _dyld_section_location_data_non_lazy_category_list,
            &count);

    // 遍历所有 non-lazy category。
    for (i = 0; i < count; i++) {

        // 取出一个 category 元数据。
        category_t *cat = categorylist[i];

        // category 的目标类。
        // 同样需要 remapClass(),避免使用旧 Class 指针。
        Class cls = remapClass(cat->cls);

        // 如果目标类无效,直接跳过。
        // category 附加到一个被忽略的 weak-linked class 上。
        if (!cls) continue;  // category for ignored weak-linked class

        // Swift stable ABI class 不允许通过 ObjC category / extension
        // 定义 +load。
        // 如果发现 Swift 类上的 category 有 +load,Runtime 直接终止。
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }

        // 在调度 category 的 +load 之前,
        // 必须确保 category 的目标类已经完成 realize。
        // realize 后,目标类的 superclass、metaclass、方法列表、
        // 属性、协议等 Runtime 结构才是稳定的。
        realizeClassWithoutSwift(cls, nil);

        // category 的 +load 是类方法层面的行为,
        // 因此目标类的元类也必须已经 realize。
        ASSERT(cls->ISA()->isRealized());

        // 将该 category 加入 Runtime 的 loadable category 列表。
        //
        // 注意:这里只是加入待调用列表,不立即调用。
        // 后续 call_load_methods() 会统一调用 category 的 +load。
        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

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

    lockdebug::assert_locked(&loadMethodLock);

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

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

getLoadMethod

objc 复制代码
objc_class::getLoadMethod()
{
    lockdebug::assert_locked(&runtimeLock);

    ASSERT(isRealized());
    ASSERT(ISA()->isRealized());
    ASSERT(!isMetaClass());
    ASSERT(ISA()->isMetaClass());

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

从元类中查找这个类自己实现的load方法,并返回它的IMP,如果没有实现load,返回nulltpr

add_category_to_loadable_list

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

    lockdebug::assert_locked(&loadMethodLock);

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

同类的流程,查找分类中的load方法,将分类中load方法加入表loadable_categories中

call_load_methods

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

    lockdebug::assert_locked(&loadMethodLock);

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

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

initialize分析

objc 复制代码
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
    lockdebug::assert_locked(&runtimeLock);
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);//会暂时释放锁
        // runtimeLock may have been dropped but is now locked again
    }

    if (slowpath(initialize && !cls->isInitialized())) {
        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;
}
objc 复制代码
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
    return initializeAndMaybeRelock(cls, obj, lock, true);
}


static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{
    lockdebug::assert_locked(&lock);
    ASSERT(cls->isRealized());

    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.
    Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);//永远发送给普通类对象,不是元类

    // Realize the non-meta class if necessary.
  //如果传进来的是元类,前面只判断了元类是否realized,不能确保类对象是否realized
    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());
    initializeNonMetaClass(nonmeta);

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

initializeNonMetaClass

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

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    Class supercls = cls->getSuperclass();
    if (supercls  &&  !supercls->isInitialized()) {//子类初始化前需要确保父类初始化完成
        initializeNonMetaClass(supercls);
    }

    // Acquire the initialization lock for this class.
    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.

    // 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.
        if (!MultithreadedForkChild || _thisThreadIsInitializingClass(cls)) {//当前线程自己重入初始化,通常发生在+initialize 内部又给本类发消息。
            unlockClass(cls);
            return;
        } else {//进程 fork 安全处理。子进程看到类处于 initializing,但那个初始化线程已经消失。所以 Runtime 需要特殊接管,执行 fork-child 初始化路径。
            // 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();//标记

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

    if (MultithreadedForkChild) {//fork进来的需要走特殊路径,保证安全性
        // 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
    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.
    //
    @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);//不管是否抛出异常,都标记完成
    }
}
  • intialize在类或者其子类的第一个方法被调用之前调用
  • 父类的initalize比子类先执行
  • 当子类未实现initialize时会调用父类的initalize方法。子类实现时会覆盖父类的initialize方法
  • 有多个分类覆盖类的initialize时,只会执行最后被加载到内存的分类的initialize方法
相关推荐
流年似水~1 小时前
iOS 开发进阶之路:从能跑到能维护
人工智能·程序人生·ios·语言模型
国科安芯1 小时前
空间辐射环境下抗辐射 MCU 可靠性机理及航空安全应用研究综述
单片机·嵌入式硬件·macos·无人机·cocos2d·risc-v
MonkeyKing2 小时前
iOS 音频会话 AVAudioSession 完整机制:分类、模式、激活策略
ios·音视频开发
一个人旅程~2 小时前
ARM版的windows(macbook虚拟机使用)在国内外技术平台有哪些版本可以选择?
windows·经验分享·macos·电脑
qq_327395033 小时前
MacOS安装openEMS
macos·openems
报错小能手15 小时前
Swift 并发 Combine响应式框架
开发语言·ios·swift
一块小土坷垃18 小时前
# 《电影猎手》观影伴侣:一款支持iOS/安卓/电视盒子的全平台影视工具“电影猎手”(附自用评价)
android·ios·电视盒子
敲代码的鱼哇20 小时前
发送短信/拨打电话/获取联系人能力 UTS 插件(cz-sms)
android·前端·ios·uni-app·安卓·harmonyos·鸿蒙
qq_4112624220 小时前
四博AI智能音响方案(基于四博小助手AITOYO2)
人工智能·macos·xcode