【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源码:
这个函数是真正处理类加载的核心函数,主要是加载类信息,包括类、分类、协议等。主要执行以下几步:
- 条件控制进行的一次加载
主要通过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");
}
- 修复预编译阶段的@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字符串可以一致,但地址必须不一致。
- 修复错误混乱的类
主要是从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,不会崩溃。
- 修复重映射一些没有被镜像文件加载进来的类
重映射:把旧的、错的、占位的类地址替换成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();
- 修复一些消息
主要是通过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
- 当类里面有协议时,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();
- 修复没有被加载的协议
主要是通过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++;
}
}
- 分类处理
主要是处理分类,分类要延后加载,不能太早,也不能太晚。分类依赖主类,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中。
- 准备工作:它先根据调试开关打印category覆盖方法和连接日志,然后为目标类创建class_rw_ext_t。
- 高效合并优化:遍历cats_list中的每个category,按当前处理的是类还是元类分别取出分类的实例方法、类方法、属性和协议。这些列表不会逐个元素拷贝,而是以method_list_t *、property_list *、protocol_list_t *为单位暂存到固定大小的栈缓冲区中,缓冲区满64个指针就批量attach到rwe->methods/properties/protocols,减少内存开销与写入次数。
- 关键逻辑:为了category覆盖顺序正确,它按照"前向遍历、反向填充缓冲区、attachLists前插"的方式组织列表,使后加载或优先级更高的category方法排在方法查找前面,确保方法查找时优先命中。
- 前置校验与准备:方法列表在附加前会调用prepareMethodLists()做selector规范化、校验、排序等准备。如果是给已存在类附加category,并且缺失新增了方法,还会刷新相关方法缓存,避免旧缓存继续命中被category替换前的IMP。
- 最终合并:最终这个函数完成的是category元数据到类运行时结构的合并。把category方法加入rwe->methods,属性加入rwe->properties,协议加入rwe->protocols,从而让目标类具备category扩展出来的行为和元数据。
- attachToClass:将分类添加到主类中。
- 类的加载处理
主要是实现类的加载处理,实现非懒加载类。
懒加载类:第一次使用的时候才去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();
- 处理没有被处理的类,优化被侵犯的类
被侵犯的类是指原本应该懒加载,现在被提前用或修改的类。如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方法:
objcstatic 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主类、分类都执行。