前言
上篇 dyld加载流程 中,核心方法是 _dyld_objc_notify_register
,是在_objc_init
中实现了通知的注册,该方法有三个参数:map_images
,load_images
,unmap_image
,下面具体看一下这三个函数的具体作用。
_objc_init
void _objc_init(void) {
// 判断初始化标记
static bool initialized = false;
if (initialized) return;
initialized = true;
// 环境变量的初始化
environ_init();
// 关于线程key的绑定
tls_init();
// 全局静态C++的构造函数的调用
static_init();
// runtime运行环境的初始化
runtime_init();
// 异常处理系统的初始化。
exception_init();
#if __OBJC2__
// 缓存类的初始化
cache_t::init();
#endif
// 启动回调机制。
_imp_implementationWithBlock_init();
// 注册dyld通知,第二个参数是load_images
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
// 完成通知dyld的注册回调
didCallDyldNotifyRegister = true;
#endif
}
environ_init()
:环境变量的初始化,主要是影响运行时的环境变量,通过环境变量可以帮助调试。tls_init()
:关于线程key的绑定,比如线程数据的构造函数。static_init()
:全局静态C++的构造函数的调用。这里只是调用objc自己的,在dyld调用之前,不是dyld调用的。runtime_init()
:runtime运行环境的初始化。主要是 unattachedCategories 和 allocatedClasses 两张表的创建exception_init()
:初始化 libobjc 的异常处理系统。cache_t::init()
:缓存类的初始化。_imp_implementationWithBlock_init()
:启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的。但是对于某些进程,比如 QtWebEngineProcess,则会初始化Trampolines库。_dyld_objc_notify_register(&map_images, load_images, unmap_image)
:加载镜像资源,加载类,加载分类等。
environ_init
void environ_init(void)
{
// 打印 OBJC_HELP 和 OBJC_PRINT_OPTIONS 输出。
if (PrintHelp || PrintOptions) {
// 打印 Settings 列表所有的选项
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
}
}
}
通过OBJC_HELP
和OBJC_PRINT_OPTIONS
打印出系统提供的环境变量。可以通过几种方法获取环境变量。
获取环境变量
- 直接方式
查看Settings
源码:
const option_t Settings[] = {
#define OPTION(var, env, help) option_t{&var, #env, help, strlen(#env)},
#include "objc-env.h"
#undef OPTION
};
Settings[]
来源于objc-env.h
,在objc
源码中,直接搜索objc-env.h
文件:
- 源码调试方式
将核心代码修改如下:
void environ_init(void)
{
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
_objc_inform("%s: %s", opt->env, opt->help);
_objc_inform("%s is set", opt->env);
}
}
打印如下:
- 终端方式
export OBJC_HELP=1
打印如下:
环境变量的使用
设置OBJC_PRINT_IMAGES
、OBJC_PRINT_LOAD_METHODS
、OBJC_DISABLE_NONPOINTER_ISA
等环境变量。
开启OBJC_PRINT_IMAGES
打印:
可以查看已经加载的
images
开启OBJC_PRINT_LOAD_METHODS
打印:
查看所有实现的
load()
方法。
开启OBJC_DISABLE_NONPOINTER_ISA
打印:
(lldb) x/4gx subObj
0x1012042a0: 0x0000000100008248 0x0000000000000000
0x1012042b0: 0x0000000780880000 0x00000001004c6228
(lldb) p/t 0x0000000100008248
(long) $1 = 0b0000000000000000000000000000000100000000000000001000001001001000
关闭OBJC_DISABLE_NONPOINTER_ISA
打印:
(lldb) x/4gx subObj
0x101304a80: 0x011d800100008249 0x0000000000000000
0x101304a90: 0x746e6f43534e5b2d 0x6f79616c206c6f72
(lldb) p/t 0x011d800100008249
(long) $1 = 0b0000000100011101100000000000000100000000000000001000001001001001
isa
最后一位,开启时是0
,关闭时是1
,也就是是否是纯指针(0
为纯指针)。
tls_init
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
// 读取线程Key
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
// 设置线程key
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
线程
key
的初始化:
- 是否支持
THREAD_KEYS
,如果支持,根据TLS_DIRECT_KEY
读取线程key
。- 如果不支持,直接创建线程
key
。
static_init
static void static_init()
{
// 根据 SECTION_TYPE 不同读取 Mach-O中不同的 section。
size_t count;
// 加载__objc_init_func,对应MachO文件中的 (__DATA,__mod_init_func)。
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
// 内存平移加载
inits[i]();
}
// 加载__objc_init_offs,对应MachO文件中的 (__TEXT,__init_offsets)。
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
// 内存平移加载
UnsignedInitializer init(offsets[i]);
init();
}
}
// getLibobjcInitializers定义
GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func");
// getLibobjcInitializerOffsets定义
uint32_t *getLibobjcInitializerOffsets(const headerType *mhdr, size_t *outCount) {
unsigned long byteCount = 0;
uint32_t *offsets = (uint32_t *)getsectiondata(mhdr, "__TEXT", "__objc_init_offs", &byteCount);
if (outCount) *outCount = byteCount / sizeof(uint32_t);
return offsets;
}
- 全局静态
C++
的构造函数的调用。在dyld
调用doModInitFunctions
静态构造函数之前,objc
调用自己的构造函数。因为此时objc
的初始化,对section
进行了替换,所以后续dyld
不会调用到这块。- 内部有两个方式(从
macho
文件读取c++
构造函数):
- 通过
__DATA,__mod_init_func
- 通过
__TEXT,__init_offsets
验证
执行了__objc_init_func
函数。
扩展
对macho
文件的读取时,macho
对应的文件名和方法对应不上,原因是在dosect (markgc.cpp)
中对section
数据进行的映射修改:
__mod_init_func
被修改称为__objc_init_func
。SECTION_TYPE
为S_MOD_INIT_FUNC_POINTERS(0x9)
__init_offsets
被修改称为__objc_init_offs
。SECTION_TYPE
为S_INIT_FUNC_OFFSETS(0x16)
__mod_term_func
被修改称为__objc_term_func
。SECTION_TYPE
为S_MOD_TERM_FUNC_POINTERS(0xa)
宏定义在#import <mach-o/loader.h>
头文件中可以找到。
#define S_MOD_INIT_FUNC_POINTERS 0x9 /* section with only function pointers for initialization*/
#define S_MOD_TERM_FUNC_POINTERS 0xa /* section with only function pointers for termination */
#define S_INIT_FUNC_OFFSETS 0x16 /* 32-bit offsets to initializers */
runtime_init
运行时环境初始化,主要是unattachedCategories
,allocatedClasses
两张表的初始化。
void runtime_init(void)
{
// 未附加的类别表
objc::unattachedCategories.init(32);
// 已加载的类表,包含的是所有`allocated`的类和元类
objc::allocatedClasses.init();
}
// 初始化表方法
init() {
template <typename... Ts>
void init(Ts &&... Args) {
new (_storage) Type(std::forward<Ts>(Args)...);
}
Type &get() {
return *reinterpret_cast<Type *>(_storage);
}
}
在后续的类和分类
的加载中会使用到。
exception_init
void exception_init(void)
{
// 设置异常捕获函数
old_terminate = std::set_terminate(&_objc_terminate);
}
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
// 当前c++异常类型不存在
if (! __cxa_current_exception_type()) {
// c++终止回调
(*old_terminate)();
} else {
// 如果是当前异常类型,判断是否是objc异常。
@try {
// 回滚作用域,重新捕获
__cxa_rethrow();
} @catch (id e) {
// 如果是objc对象,错误回调处理
(*uncaught_handler)((id)e);
// c++终止回调
(*old_terminate)();
} @catch (...) {
// 如果不是objc对象,执行c++终止回调
(*old_terminate)();
}
}
}
从源码分析:当执行
exception_init
时会挂载一个异常处理的回调uncaught_handler
,或许可以通过uncaught_handler
截取异常崩溃信息。
uncaught_handler
static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;
objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
// 设置未捕获的objc对象的异常处理的回调
objc_uncaught_exception_handler result = uncaught_handler;
uncaught_handler = fn;
// 返回回调
return result;
}
异常捕获案例
根据uncaught_handler
函数可以实现异常捕获类的封装:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 注册异常监听
[ZLCrashCapturer registerCrashCapture];
return YES;
}
在didFinishLaunchingWithOptions
实现异常捕获类的注册。
+ (void)registerCrashCapture {
NSSetUncaughtExceptionHandler(&ZLUncaughtException);
}
// 挂载回调函数
void ZLUncaughtException(NSException *exception) {
// 保存崩溃信息到本地
saveLocal();
// 回调处理
if (handler) {
handler(exception);
}
}
通过注册回调函数,当发生异常时,会实时回调给ZLUncaughtException
函数,进而记录异常信息,并回调。
注意:
- 该方法只能捕获
oc对象
的异常。objc_setUncaughtExceptionHandler
对应上层方法NSSetUncaughtExceptionHandler
。- 自定义的
监听方法
尽量放在didFinishLaunchingWithOptions
的最后面,防止其它SDK
覆盖。过多SDK
调用NSSetUncaughtExceptionHandler
有时会混乱,表现是捕获到的crash
没有符号表。
cache_t::init
缓存类的初始化
#if TARGET_OS_SIMULATOR || defined(__i386__) || defined(__arm__) || !TARGET_OS_MAC
# define HAVE_TASK_RESTARTABLE_RANGES 0
#else
# define HAVE_TASK_RESTARTABLE_RANGES 1
#endif
void cache_t::init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
mach_msg_type_number_t count = 0;
kern_return_t kr;
// 获取重启表的位置
while (objc_restartableRanges[count].location) {
count++;
}
// 重启表的位置注册
kr = task_restartable_ranges_register(mach_task_self(),
objc_restartableRanges, count);
if (kr == KERN_SUCCESS) return;
#endif
}
_imp_implementationWithBlock_init
启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的。但是对于某些进程,比如 QtWebEngineProcess
或者Steam Helper
,则会初始化Trampolines
库。
void _imp_implementationWithBlock_init(void) {
#if TARGET_OS_OSX
if (__progname &&
(strcmp(__progname, "QtWebEngineProcess") == 0 ||
strcmp(__progname, "Steam Helper") == 0)) {
Trampolines.Initialize();
}
#endif
}
_dyld_objc_notify_register
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
_dyld_objc_notify_register
注册了map_images
、load_images
、unmap_image
的回调。其实现是在dyld
初始化主程序完成时调用notifySingle
。
map_images
:管理文件中和动态库中所有的符号 (class
、Protocol
、selector
、category
)。load_images
:加载执行load
方法unmap_image
:释放类相关资源。
这里的
map_images
与load_images
传递的参数方式不同,map_images
是指针拷贝,load_images
是值传递。因为map_images
需要同步变化,它非常重要,是映射镜像文件方法,同步变化是避免发生错乱的可能性。而load_images
比较简单只是load
的调用,不需要同步变化。
map_images分析
void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
// 创建锁
mutex_locker_t lock(runtimeLock);
// 调用 map_images_nolock
return map_images_nolock(count, paths, mhdrs);
}
map_images_nolock
其中map_images_nolock
核心代码为_read_images
:
void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
if (hCount > 0) {
// 读取镜像文件
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
_read_images 流程分析
_read_images
是管理文件中和动态库中所有的符号(class
,protocols
,selector
,categories
)
对
_read_images
函数源码进行代码块折叠后发现,每一个log
说明了该代码快的作用。主要的步骤如下:
- 条件控制进行一次的加载。(
doneOnce
)- 修复预编译阶段的
@selector
的混乱问题- 错误混乱的类处理
- 修复重映射一些没有被镜像文件加载进来的类
- 修复一些消息
- 当类里面有协议的时候 :
readProtocol
加载协议- 修复没有被加载的协议
- 分类处理(
重点
)- 类的加载处理(
重点
)- 没有被处理的类的优化
doneOnce
if (!doneOnce) {
doneOnce = YES;
// 初始化小对象类型的混淆处理
initializeTaggedPointerObfuscator();
// 获取开辟的内存容量,相当于是负载因子3/4的逆过程。
// totalClasses总容量,unoptimizedTotalClasses要占用的空间。
int namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
// 创建一张总表,包括在runtime_init创建的两张表,包含关系
gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
}
taggedPointer
混淆- 通过负载因子
load factor
计算总表大小- 创建总表,包含
runtime_init
创建的unattachedCategories
和allocatedClasses
表- 总表
gdb_objc_realized_classes
,无论类是否实例化。
总结:
doneOnce
主要作用创建总表(只执行一次)。
unfixedSelectors
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) continue;
bool isBundle = hi->isBundle();
// 从macho读取所有的sels符号表
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
// 从dyld中获取sel
SEL sel = sel_registerNameNoLock(name, isBundle);
if (sels[i] != sel) {
// 地址比较,对地址的修复
sels[i] = sel;
}
}
}
sel
是在dyld
加载的,sels[i]
是从macho
读出来的,从macho
读出来的地址可能是混乱的。sels[i] = sel
是对地址进行覆盖,因为在dyld
加载出来的sel
地址是有效的。- 查找
sel
的调用路径:sel_registerNameNoLock
→__sel_registerName
→search_builtins
→_dyld_get_objc_selector
。明显看出sel
由_dyld_get_objc_selector
获取的。
总结:
unfixedSelectors
主要作用修复sel
地址混乱问题。
在sels[i] = sel
打断点,验证:
discover classes
for (EACH_HEADER) {
// 从macho读取ClassList
classref_t const *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
// 读取类(重点)
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
// 只有当新类解析了将来的类时,才会执行
if (newCls != cls && newCls) {
// 当类已经移除(出现空内存),但是并没有删除。
// 非懒加载实现未来类的决议
resolvedFutureClasses = (Class *)realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
- 从
macho
加载类的表信息- 通过
readClass
读取类- 未来类的决议
在readClass
前后打断点,分别验证:
总结:
discover classes
主要作用调用readClass
加载类信息。(后面具体分析readClass
)
remap classes
for (EACH_HEADER) {
// 读取macho的类引用表
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
// 重映射类引用,以防引用的类被重新分配
remapClassRef(&classrefs[i]);
}
// 读取macho的父类引用表
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
// 重映射父类引用,以防引用的父类被重新分配
remapClassRef(&classrefs[i]);
}
}
- 从
macho
分别读取类
引用表和父类
引用表- 调用
remapClassRef
重映射类的引用,防止引用的类被重新分配remapClassRef()
→remapClass()
重映射类引用。
总结:
remap classes
主要作用重映射类及父类引用,防止被重新分配
objc_msgSend_fixup
for (EACH_HEADER) {
// 获取macho中sel方法表
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
for (i = 0; i < count; i++) {
// 修复方法引用(映射方法),比如上层调用alloc,底层调用的是objc_alloc
// 此处为了防止出错,其实llvm在编译期间就做了,这是是防止有其他影响变了,所以进行修复
fixupMessageRef(refs+i);
}
}
- 获取
macho
中sel
方法表- 重映射方法引用(映射方法)
- 正常情况下不会走这里,因为方法映射在
llvm
阶段已经处理了。
总结:
objc_msgSend_fixup
主要作用映射sel
引用
discover protocols
for (EACH_HEADER) {
// 创建协议表(如果当前类有协议的话)
NXMapTable *protocol_map = protocols();
bool isBundle = hi->isBundle();
// 从macho获取协议表
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
// 读取协议,并重映射协议
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
NXMapTable
创建协议表- 获取
macho
协议列表- 重映射协议,并存储到协议表中
总结:
discover protocols
主要作用读取协议
readProtocol
static void
readProtocol(protocol_t *newproto, Class protocol_class,
NXMapTable *protocol_map,
bool headerIsPreoptimized, bool headerIsBundle)
{
// 插入表方法
auto insertFn = headerIsBundle ? NXMapKeyCopyingInsert : NXMapInsert;
// 获取旧的协议
protocol_t *oldproto = (protocol_t *)getProtocol(newproto->mangledName);
if (oldproto) {
// 如果存在旧的协议,且不等于新的协议
if (oldproto != newproto) {
if (headerIsPreoptimized && !oldproto->isCanonical()) {
// 获取缓存的协议,也就是旧的协议
auto cacheproto = (protocol_t *)
getSharedCachePreoptimizedProtocol(newproto->mangledName);
if (cacheproto && cacheproto->isCanonical())
// 清除协议(通过与运算)
cacheproto->clearIsCanonical();
}
}
} else if (headerIsPreoptimized) {
// 获取预优化协议
protocol_t *cacheproto = (protocol_t *)
getPreoptimizedProtocol(newproto->mangledName);
protocol_t *installedproto;
if (cacheproto && cacheproto != newproto) {
installedproto = cacheproto;
} else {
installedproto = newproto;
}
// 如果存在预加载协议,则插入;否则插入新的协议
insertFn(protocol_map, installedproto->mangledName,
installedproto);
} else {
// 初始化协议类
newproto->initIsa(protocol_class); // fixme pinned
// 插入新协议
insertFn(protocol_map, newproto->mangledName, newproto);
}
}
- 判断是否存在
旧的协议
,如果存在则清除
。- 如果不存在且有
预优化标记
,查看预优化缓存协议,如果存在,则插入
,否则插入新的
。- 如果都没有,则
创建
新的协议类,并插入
新协议。
fix up @protocol references
for (EACH_HEADER) {
// 获取macho协议引用列表
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
// 重映射协议引用,防止协议被重新分配
remapProtocolRef(&protolist[i]);
}
}
- 获取
macho
中协议
引用表- 重映射协议引用,防止协议被重新分配。(自定义的协议不会走到这里)
总结:
fix up @protocol references
主要作用重映射协议
引用
discover categories
if (didInitialAttachCategories) {
for (EACH_HEADER) {
// 加载分类
load_categories_nolock(hi);
}
}
- 仅在初始类别之后执行此操作,因此此处不会执行。
- 推迟到
load_images
调用。
总结:
discover categories
并不会加载分类
,分类的加载必须在load_images
之后
realize non-lazy classes
for (EACH_HEADER) {
// 获取macho非懒加载的列表
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
// 映射非懒加载类
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// 添加非懒加载类到 allocatedClasses 表,该方法默认添加类的元类。
addClassTableEntry(cls);
// 初始化非懒加载类
realizeClassWithoutSwift(cls, nil);
}
}
- 获取
macho
非懒加载的类的列表- 加载类,并添加到
allocatedClasses
表- 初始化非懒加载类(
realizeClassWithoutSwift
函数后续分析)- 一般情况下,类实现了
+ load
方法,会进入。因为+load
方法的类会出现在__objc_nlclslist
中。
非懒加载类分为三种情况:
- 自己实现了
+ load
方法(会出现在__objc_nlclslist
中)- 子类实现了
+ load
方法(不会出现在__objc_nlclslist
中)- 分类实现了
+ load
方法(包括自己的分类以及子类的分类,因为分类的加载不是在map_images
流程,而是在load_images
流程。因此不会出现在__objc_nlclslist
中)
总结:
realize non-lazy classes
主要作用初始化非懒加载类
realize future classes
// 实现新解析的未来类,以防CF操纵它们
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");
}
// 初始化未来类
realizeClassWithoutSwift(cls, nil);
cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
}
// 释放内存
free(resolvedFutureClasses);
}
realize future classes
初始化future
类,正常不会执行此逻辑。
readClass重点分析
加载类和元类
,主要是进行了地址关联
,将地址关联到类
的操作。
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
// 如果没有父类(可能是弱链接的)。
// 通常情况下,不会执行这里
if (missingWeakSuperclass(cls)) {
// 重新绑定父类为nil
addRemappedClass(cls, nil);
// 设置父类为nil
cls->setSuperclass(nil);
return nil;
}
// 替换类
Class replacing = nil;
if (mangledName != nullptr) {
// 根据类名获取未来类
// 通常情况下,不会执行这里
if (Class newCls = popFutureNamedClass(mangledName)) {
...
}
}
if (headerIsPreoptimized && !replacing) {
// 断言 cls == getClass(name)
ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
} else {
if (mangledName) {
// 关联类信息,加入总表 gdb_objc_realized_classes
addNamedClass(cls, mangledName, replacing);
} else {
// 断言非元类是否存在
}
// 加入 cls 到 allocatedClasses 表
addClassTableEntry(cls);
}
// 通常情况下,不会执行这里
if (headerIsBundle) {
// 设置标志位
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}
该方法中虽然判断很多,但是通过断点分析,正常情况下,会执行
addNamedClass
,addClassTableEntry
。
addNamedClass
关联类信息,类和地址的关联,并加入总表gdb_objc_realized_classes
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
Class old;
// 按名称查找,获取可能未实现的类。
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);
// 未按名称查找的类存于非元类表 nonmeta_class_map
addNonMetaClass(cls);
} else {
// 如果没找到,插入到 gdb_objc_realized_classes 表
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
}
- 主要目的是将
cls
插入到gdb_objc_realized_classes
总表中(doneOnce
中创建的总表)NXMapInsert
处理了关联类与地址。在插入总表的时候,进行MapPair(key-value)
的形式关联。
NXMapInsert
typedef struct _MapPair {
const void *key;
const void *value;
} MapPair;
void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
// 获取buckets
MapPair *pairs = (MapPair *)table->buckets;
unsigned index = bucketOf(table, key);
// 找到对应的pair
MapPair *pair = pairs + index;
// 新buckets地址
unsigned numBuckets = table->nbBucketsMinusOne + 1;
if (isEqual(table, pair->key, key)) {
const void *old = pair->value;
// 非必需避免写入
if (old != value) pair->value = value;
return (void *)old;
} else if (table->count == numBuckets) {
// 没有空间,重新hash
_NXMapRehash(table);
return NXMapInsert(table, key, value);
} else {
unsigned index2 = index;
while ((index2 = nextIndex(table, index2)) != index) {
pair = pairs + index2;
if (pair->key == NX_MAPNOTAKEY) {
pair->key = key; pair->value = value;
table->count++;
// 判断负载因子
if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
return NULL;
}
if (isEqual(table, pair->key, key)) {
const void *old = pair->value;
// 非必需避免写入
if (old != value) pair->value = value;
return (void *)old;
}
}
return NULL;
}
}
该方法的主要作用是将
类
加入到总表
中,并做地址关联
。
addClassTableEntry
加入cls
到allocatedClasses
表,默认也会将元类
加入其中。
static void addClassTableEntry(Class cls, bool addMeta = true)
{
// 获取allocatedClasses表
auto &set = objc::allocatedClasses.get();
// 如果类不存在,则直接插入
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
// 默认插入元类,但元类的元类不在插入
addClassTableEntry(cls->ISA(), false);
}
该方法的主要作用是将
类
和元类
加入到allocatedClasses
表中(runtime_init
中创建的allocatedClasses
表)。
小结
readClass
的核心逻辑是将类与地址关联
,并加入gdb_objc_realized_classes
与allocatedClasses
表中
总结
承接dyld加载流程,类的加载流程如下:
补充
懒加载和非懒加载
懒加载和非懒加载类的说明:
- 为了按需分配,启动过程中初始化的类
越少
启动速度就越快
。- 对于非懒加载类实现了
+load
方法(子类/分类/自己),类就会提前加载,为+ load
的调用做准备。- 对于懒加载类,是在第一次消息发送
objc_msgSend
,进行lookUpImpOrForward
消息慢速查找的时候进行初始化的。
懒加载和非懒加载区别:
懒加载类
:数据加载推迟到第一次发送消息的时候。
lookUpImpOrForward
realizeClassMaybeSwiftMaybeRelock
realizeClassWithoutSwift
methodizeClass
非懒加载类
:map_images
的时候加载所有类数据。
readClass
_getObjc2NonlazyClassList
realizeClassWithoutSwift
methodizeClass