上篇文章中分析了dyld
整个流程以及dyld
与objc
的交互。这篇文章将继续分析dyld
调用map_images
究竟进行了什么操作。
一、_objc_init分析
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
//环境变量的初始化
environ_init();
//tls 关于线程key的绑定,比如线程数据的析构函数
tls_init();
//全局静态c++函数调用,这里只是调用objc自己的。在dyld调用之前,相当于objc的c++构造函数是自己调用的,不是dyld调用的。
static_init();
//runtime相关两张表的初始化。
runtime_init();
//初始化 libobjc 的异常处理系统。
exception_init();
#if __OBJC2__
//缓存条件初始化
cache_t::init();
#endif
//启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
_imp_implementationWithBlock_init();
//map_images:管理文件中和动态库中所有的符号 (class Protocol selector category)
//load_images:加载执行load方法
//unmap_image:释放类相关资源。
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
-
environ_init
:环境变量的初始化,可以通过配置打印查看。 -
tls_init
:关于线程key
的绑定,比如线程数据的析构函数` -
static_init
:全局静态c++
函数调用,这里只是调用objc
自己的。在dyld
调用之前,相当于objc
的c++
构造函数是自己调用的,不是dyld
调用的。 -
runtime_init
:runtime
相关两张表的初始化。 -
exception_init
:初始化libobjc
的异常处理系统。 -
cache_t::init
:缓存条件初始化。 -
_imp_implementationWithBlock_init
:启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,会迫不及待地加载trampolines dylib
。 -
_dyld_objc_notify_register
:注册map_images
、load_images
、unmap_image
的回调。
1.1 environ_init
environ_init
中进行了一些环境变量的初始化,核心逻辑如下:
void environ_init(void)
{
……
if (PrintHelp || PrintOptions) {
……
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);
}
}
}
可以看到是否打印环境变量是通过PrintHelp || PrintOptions
控制的。有三种方式打印出当前的环境变量配置。
1.1.1 修改代码输出日志信息
直接将打印的代码拷贝到environ_init
函数内部前面修改运行就可以输出环境变量信息了。
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);
}
输出:
1.1.2 终端输出
export OBJC_HELP=1
➜ ~ export OBJC_HELP=1
➜ ~ cd Desktop/
- 先导出
OBJC_HELP
,然后执行一个终端命令就会打印出环境变量了。
1.1.3 源码文件查看
在objc
源码objc-env.h
文件中可以查看。探索过程可以参考OC对象isa部分的分析逻辑。
1.1.4 环境变量验证
在环境变量中有两个配置:OBJC_DISABLE_NONPOINTER_ISA
与OBJC_PRINT_LOAD_METHODS
。直接在scheme->Arguments->Environment Variables
中配置。
OBJC_DISABLE_NONPOINTER_ISA:配置是否开启NONPOINTER
指针。
//开启前:
(lldb) x/4gx obj
0x101c6bf30: 0x011d800100008269 0x0000000000000000
0x101c6bf40: 0x697263534b575b2d 0x67617373654d7470
(lldb) p/t 0x011d800100008269
(long) $2 = 0b0000000100011101100000000000000100000000000000001000001001101001
//开启后:
(lldb) x/4gx obj
0x10103ff00: 0x0000000100008268 0x0000000000000000
0x10103ff10: 0x697263534b575b2d 0x67617373654d7470
(lldb) p/t 0x0000000100008268
(long) $1 = 0b0000000000000000000000000000000100000000000000001000001001101000
- 可以看到
isa
最后一位在开启前是1
开启后变为了0
。也就是是否是纯指针。
OBJC_PRINT_LOAD_METHODS:打印所有实现了+ load
方法的类信息。
objc[39927]: LOAD: class 'NSApplication' scheduled for +load
objc[39927]: LOAD: class 'NSBinder' scheduled for +load
objc[39927]: LOAD: class 'NSColorSpaceColor' scheduled for +load
objc[39927]: LOAD: class 'NSNextStepFrame' scheduled for +load
objc[39927]: LOAD: category 'NSColor(NSUIKitSupport)' scheduled for +load
objc[39927]: LOAD: +[NSApplication load]
objc[39927]: LOAD: +[NSBinder load]
objc[39927]: LOAD: +[NSColorSpaceColor load]
objc[39927]: LOAD: +[NSNextStepFrame load]
objc[39927]: LOAD: +[NSColor(NSUIKitSupport) load]
objc[39927]: LOAD: category 'NSError(FPAdditions)' scheduled for +load
objc[39927]: LOAD: +[NSError(FPAdditions) load]
objc[39927]: LOAD: class '_DKEventQuery' scheduled for +load
objc[39927]: LOAD: +[_DKEventQuery load]
所以直接配置就可以知道哪些类实现了+ load
方法。可以监控所有的+load
方法,从而处理启动优化。
1.2 tls_init
关于线程key
的绑定,比如线程数据的析构函数。
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
1.3 static_init
运行C++
静态构造函数。在dyld
调用静态构造函数之前,libc
会调用 _objc_init()
, 因此必须自己做。
/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors,
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
//这两个是根据SECTION_TYPE不同读取不同的section,当为S_MOD_INIT_FUNC_POINTERS时读取__mod_init_funcs,当为S_INIT_FUNC_OFFSETS(0x16)时读取__init_offsets
size_t count;
//查找__objc_init_func,对应macho文件中的 __DATA __mod_init_func。
//调用c++构造函数,这里是通过下标平移获取
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
//查找__TEXT __objc_init_offs,对应macho文件中的 __TEXT __init_offsets
//调用c++构造函数,通过偏移值获取
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
UnsignedInitializer init(offsets[i]);
init();
}
}
GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func");
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++
函数调用,这里只是调用objc
自己的。在dyld doModInitFunctions
调用之前,相当于objc
的c++
构造函数是自己调用的,不是dyld
调用的。因为dyld
调用的时机太晚了,objc
对这个section进行了替换,所以后续
dyld`不会调用到这块。 - 内部有两个逻辑一个是通过
__mod_init_funcs
,一个是通过__init_offsets
从macho
文件读取c++
构造函数。目前暂不清楚什么情况下会走__init_offsets
的逻辑。
直接在源码中添加一个c++
构造函数进行验证:
上面对macho
文件的读取发现和macho
文件是对不上的,在dosect (markgc.cpp)
中发现了对macho
文件对应的section
数据进行的映射修改:
template
void dosect(uint8_t *start, macho_section *sect)
{
if (debug) printf("section %.16s from segment %.16s\n",
sect->sectname(), sect->segname());
// Strip S_MOD_INIT/TERM_FUNC_POINTERS. We don't want dyld to call
// our init funcs because it is too late, and we don't want anyone to
// call our term funcs ever.
if (segnameStartsWith(sect->segname(), "__DATA") &&
sectnameEquals(sect->sectname(), "__mod_init_func"))
{
// section type 0 is S_REGULAR
sect->set_flags(sect->flags() & ~SECTION_TYPE);
sect->set_sectname("__objc_init_func");
if (debug) printf("disabled __mod_init_func section\n");
}
if (segnameStartsWith(sect->segname(), "__TEXT") &&
sectnameEquals(sect->sectname(), "__init_offsets"))
{
// section type 0 is S_REGULAR
sect->set_flags(sect->flags() & ~SECTION_TYPE);
sect->set_sectname("__objc_init_offs");
if (debug) printf("disabled __mod_init_func section\n");
}
if (segnameStartsWith(sect->segname(), "__DATA") &&
sectnameEquals(sect->sectname(), "__mod_term_func"))
{
// section type 0 is S_REGULAR
sect->set_flags(sect->flags() & ~SECTION_TYPE);
sect->set_sectname("__objc_term_func");
if (debug) printf("disabled __mod_term_func section\n");
}
}
-
__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)
他们的区别是获取func
的方式不同:
//S_MOD_INIT_FUNC_POINTERS
Initializer* inits = (Initializer*)(sect->addr + fSlide);
Initializer func = inits[j];
//S_INIT_FUNC_OFFSETS
Initializer func = (Initializer)((uint8_t*)this->machHeader() + funcOffset);
-
__mod_term_func
被修改称为__objc_term_func
。这个也就是Terminators
。SECTION_TYPE
为S_MOD_TERM_FUNC_POINTERS(0xa)
。这个实现了destructor
函数就有了。 - 这个函数最终是被自己的
main
所调用的。注释中已经说明不想被dyld
调用,所以进行了strip
,因为dyld
调用的时机太晚了。 - 通过查看
macho
文件,应该在编译期就替换了。
宏定义在
#import
头文件中可以找到。#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*/
1.4 runtime_init
runtime
运行时环境初始化,主要是unattachedCategories
,allocatedClasses
两张表的初始化。
void runtime_init(void)
{
//两张储存表的初始化
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
// ExplicitInit / LazyInit wrap doing it the hard way.
template
class ExplicitInit {
alignas(Type) uint8_t _storage[sizeof(Type)];
public:
template
void init(Ts &&... Args) {
new (_storage) Type(std::forward(Args)...);
}
Type &get() {
return *reinterpret_cast(_storage);
}
};
这块内容会在后续分析。
1.5 exception_init
初始化libobjc
的异常处理系统。
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;
/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler.
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
**********************************************************************/
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 exception回调
(*uncaught_handler)((id)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
-
exception_init
初始化了_objc_terminate
进行异常处理。 - 底层在处理的时候发现了处理不了的
exp
,也就是异常会调用uncaught_handler
回调。
这个通过个案例就清晰了。
1.5.1 exception异常捕获
有以下代码:
self.dataArray = [@[@1,@2,@3,@4] mutableCopy];
NSLog(@"%@",self.dataArray[4]);
在运行的时候回发生crash
,可以通过注册异常回调监听到整个crash
,具体实现如下:
#include
#include
#include
//异常名称key
NSString * const HPUncaughtExceptionHandlerSignalExceptionName = @"HPUncaughtExceptionHandlerSignalExceptionName";
//异常原因key
NSString * const HPUncaughtExceptionHandlerSignalExceptionReason = @"HPUncaughtExceptionHandlerSignalExceptionReason";
//bt精简过的
NSString * const HPUncaughtExceptionHandlerAddressesKey = @"HPUncaughtExceptionHandlerAddressesKey";
//异常文件key
NSString * const HPUncaughtExceptionHandlerFileKey = @"HPUncaughtExceptionHandlerFileKey";
//异常符号
NSString * const HPUncaughtExceptionHandlerCallStackSymbolsKey = @"HPUncaughtExceptionHandlerCallStackSymbolsKey";
atomic_int HPUncaughtExceptionCount = 0;
const int32_t HPUncaughtExceptionMaximum = 8;
const NSInteger HPUncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger HPUncaughtExceptionHandlerReportAddressCount = 5;
//保存原先的handler
NSUncaughtExceptionHandler *originalUncaughtExceptionHandler = NULL;
@interface HPUncaughtExceptionHandler()
+ (NSArray *)backtrace;
- (void)handleUncaughtSignalException:(NSException *)exception;
@end
// exception 调用时机来自 _objc_terminate
void HPExceptionHandlers(NSException *exception) {
NSLog(@"%s",__func__);
int32_t exceptionCount = atomic_fetch_add_explicit(&HPUncaughtExceptionCount,1,memory_order_relaxed);
if (exceptionCount > HPUncaughtExceptionMaximum) {
return;
}
// 获取堆栈信息
NSArray *callStack = [HPUncaughtExceptionHandler backtrace];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:exception.name forKey:HPUncaughtExceptionHandlerSignalExceptionName];
[userInfo setObject:exception.reason forKey:HPUncaughtExceptionHandlerSignalExceptionReason];
[userInfo setObject:callStack forKey:HPUncaughtExceptionHandlerAddressesKey];
[userInfo setObject:exception.callStackSymbols forKey:HPUncaughtExceptionHandlerCallStackSymbolsKey];
[userInfo setObject:@"HPUncaughtException" forKey:HPUncaughtExceptionHandlerFileKey];
[[[HPUncaughtExceptionHandler alloc] init]
performSelectorOnMainThread:@selector(handleUncaughtSignalException:)
withObject:
[NSException
exceptionWithName:[exception name]
reason:[exception reason]
userInfo:userInfo]
waitUntilDone:YES];
//处理完自己的调用之前的。
if (originalUncaughtExceptionHandler) {
originalUncaughtExceptionHandler(exception);
}
}
@implementation HPUncaughtExceptionHandler
+ (void)installUncaughtSignalExceptionHandler {
//可以通过 NSGetUncaughtExceptionHandler 先保存旧的,然后赋值自己新的。
if (NSGetUncaughtExceptionHandler() != HPExceptionHandlers) {
originalUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
}
//HPExceptionHandlers 赋值给 uncaught_handler(),最终_objc_terminate 调用 HPExceptionHandlers
//NSSetUncaughtExceptionHandler 是 objc_setUncaughtExceptionHandler()的上层实现。
NSSetUncaughtExceptionHandler(&HPExceptionHandlers);
}
+ (void)removeRegister:(NSException *)exception {
NSSetUncaughtExceptionHandler(NULL);
[exception raise];
}
- (void)handleUncaughtSignalException:(NSException *)exception {
// 保存上传服务器
NSDictionary *userinfo = [exception userInfo];
[self saveCrash:exception file:[userinfo objectForKey:HPUncaughtExceptionHandlerFileKey]];
//alert 提示相关操作
//如果要做 UI 相关提示需要写runloop相关的代码
//移除注册
[HPUncaughtExceptionHandler removeRegister:exception];
}
//保存奔溃信息或者上传
- (void)saveCrash:(NSException *)exception file:(NSString *)file {
NSArray *stackArray = [[exception userInfo] objectForKey:HPUncaughtExceptionHandlerCallStackSymbolsKey];// 异常的堆栈信息
NSString *reason = [exception reason];// 出现异常的原因
NSString *name = [exception name];// 异常名称
// NSLog(@"crash: %@", exception);// 可以在console 中输出,以方便查看。
NSString * filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:file];
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]){
[[NSFileManager defaultManager] createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:nil];
}
NSDate *dat = [NSDate dateWithTimeIntervalSinceNow:0];
NSTimeInterval timeInterval = [dat timeIntervalSince1970];
NSString *timeString = [NSString stringWithFormat:@"%f", timeInterval];
NSString *savePath = [filePath stringByAppendingFormat:@"/error_%@.log",timeString];
NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];
BOOL sucess = [exceptionInfo writeToFile:savePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"save crash log sucess:%d, path:%@",sucess,savePath);
//保存之后可以做上传相关操作。
}
//获取函数堆栈信息
+ (NSArray *)backtrace {
void* callstack[128];
int frames = backtrace(callstack, 128);//用于获取当前线程的函数调用堆栈,返回实际获取的指针个数
char **strs = backtrace_symbols(callstack, frames);//从backtrace函数获取的信息转化为一个字符串数组
int i;
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
//过滤部分数据,从4~8取5个。
for (i = HPUncaughtExceptionHandlerSkipAddressCount;
i < HPUncaughtExceptionHandlerSkipAddressCount + HPUncaughtExceptionHandlerReportAddressCount;
i++)
{
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
@end
- 通过
NSGetUncaughtExceptionHandler
与NSSetUncaughtExceptionHandler
进行异常回调的注册,NSGetUncaughtExceptionHandler
保留之前的回调(其它SDK
可能实现了)。要自己处理完后回调给对方。还可以对NSSetUncaughtExceptionHandler
函数进行hook
,自己处理完后再调用原始调用。 - 在处理逻辑中可以先进行本地的保存,然后做相关的提示(需要自己创建
runloop
),最后需要移除注册的回调。 - 这样详细信息就可以获取到了。
注意点:
- 只能捕获
OC
抛出的异常,signal
无法捕获,signal
异常可以设置signal
的handler
。 - 自己的
NSSetUncaughtExceptionHandler
尽量放在didFinishLaunchingWithOptions
的最后面,防止其它SDK
覆盖。过多SDK
调用NSSetUncaughtExceptionHandler
有时会混乱,表现是捕获到的crash
没有符号表。
1.5.2 signal 捕获
可以通过sigaction
/signal
注册对应的信号回调,注册后可以构建异常信息进行处理,流程与exception
差不多。
signal简单实现
+ (void)registerSignalHandler {
signal(SIGHUP, HPSignalHandler);
signal(SIGINT, HPSignalHandler);
signal(SIGQUIT, HPSignalHandler);
signal(SIGABRT, HPSignalHandler);
signal(SIGILL, HPSignalHandler);
signal(SIGSEGV, HPSignalHandler);
signal(SIGFPE, HPSignalHandler);
signal(SIGBUS, HPSignalHandler);
signal(SIGPIPE, HPSignalHandler);
}
// signal处理方法
void HPSignalHandler(int signal) {
int32_t exceptionCount = atomic_fetch_add_explicit(&HPUncaughtExceptionCount,1,memory_order_relaxed);
if (exceptionCount > HPUncaughtExceptionCount) {
return;
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:HPUncaughtExceptionHandlerSignalKey];
NSArray *callStack = [HPUncaughtExceptionHandler backtrace];
[userInfo setObject:callStack forKey:HPUncaughtExceptionHandlerAddressesKey];
[userInfo setObject:@"HPSignalCrash" forKey:HPUncaughtExceptionHandlerFileKey];
[userInfo setObject:callStack forKey:HPUncaughtExceptionHandlerCallStackSymbolsKey];
[[[HPUncaughtExceptionHandler alloc] init]
performSelectorOnMainThread:@selector(handleUncaughtSignalException:) withObject:
[NSException
exceptionWithName:HPUncaughtExceptionHandlerSignalExceptionName
reason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.\n %@", nil),signal, getAppInfo()]
userInfo:userInfo]
waitUntilDone:YES];
sigaction简单实现
+ (void)registerSigactionHandler {
struct sigaction old_action;
sigaction(SIGABRT, NULL, &old_action);
if (old_action.sa_flags & SA_SIGINFO) {
if (old_action.sa_sigaction != HPAbrtSignalHandler) {
//保存之前注册的handler
originalAbrtSignalHandler = old_action.sa_sigaction;
}
}
struct sigaction action;
action.sa_sigaction = HPAbrtSignalHandler;
action.sa_flags = SA_NODEFER | SA_SIGINFO;
sigemptyset(&action.sa_mask);
sigaction(SIGABRT, &action, 0);
}
//保存原先abrt的handler
void (*originalAbrtSignalHandler)(int, struct __siginfo *, void *);
static void HPAbrtSignalHandler(int signal, siginfo_t* info, void* context) {
HPSignalHandler(signal);
//调用之前注册的handler
if (signal == SIGABRT && originalAbrtSignalHandler) {
originalAbrtSignalHandler(signal, info, context);
}
}
sigaction
和signal
函数的区别:
-
signal
在系统调用的基础上实现,是库函数。只有两个参数,不支持信号传递信息,主要是用于前32个非实时信号。 -
sigaction
是较新的函数(由两个系统调用实现:sys_signal
以及sys_rt_sigaction
)。有三个参数,支持信号传递信息,主要用来与sigqueue
系统调用配合使用。sigaction
同样支持非实时信号。 -
sigaction
支持信号带有参数。signal
使用简单,sigaction
使用相对复杂一些。
测试代码:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//exception错误
// NSLog(@"%@",self.dataArray[4]);
//signal 错误
void *singal = malloc(1024);
free(singal);
free(singal);//SIGABRT的错误
}
⚠️在
debug
模式下,signal
应用会直接崩溃到主函数,想看log信息需要在lldb
中进行配置,以SIGABRT
为例,先断点输入pro hand -p true -s false SIGABRT
后继续运行,再执行到crash
逻辑。这样就能断住断点了:
最终crash
信息会保存在Caches
文件中:
完整demo
1.6 cache_t::init
cache_t::init
缓存条件初始化。
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;
_objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}
1.7 _imp_implementationWithBlock_init
启动回调机制。通常不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,会迫不及待地加载trampolines dylib
。
void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
if (__progname &&
(strcmp(__progname, "QtWebEngineProcess") == 0 ||
strcmp(__progname, "Steam Helper") == 0)) {
Trampolines.Initialize();
}
#endif
}
1.8 _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
中。
-
map_images
:管理文件中和动态库中所有的符号 (class
、Protocol
、selector
、category
)。 -
load_images
:加载执行load
方法 -
unmap_image
:释放类相关资源。
这里有个疑问,在dyld
中map_images
与load_images
的调用没有区别。但是在这里传递的参数map_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);
return map_images_nolock(count, paths, mhdrs);
}
map_images
只是对map_images_nolock
的调用。
2.1 map_images_nolock
map_images_nolock
的核心逻辑是要找镜像文件是怎么被加载的,也就是对class
、 Protocol
、 selector
、 category
等相关的操作。在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分析
_read_images
的实现有360
行,将函数实现内的if-else
代码折叠起来后会有惊喜:
根据官方的
log
信息可以大概了解每个代码块的内容。(这里为了方便截图,我拷贝了_read_images
方法删除了注释进行了精简。)
主要内容如下:
- 1.条件控制进行一次的加载。(
doneOnce
) - 2.修复预编译阶段的
@selector
的混乱问题。 - 3.错误混乱的类处理 。
- 4.修复重映射一些没有被镜像文件加载进来的类 。
- 5.修复一些消息。
- 6.当我们类里面有协议的时候 :
readProtocol
。 - 7.修复没有被加载的协议。
- 8.分类处理。
- 9.类的加载处理。
- 10.没有被处理的类的优化。
显然8
和9
是核心。也就是load_categories_nolock
与realizeClassWithoutSwift
。
3.1 doneOnce 分析
doneOnce
核心逻辑如下:
//小对象类型的处理。
initializeTaggedPointerObfuscator();
// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
//创建一张表,容量: x = totalClass * 4 / 3(totalClass = x * 3 / 4)。相当于是负载因子3/4的逆过程。namedClassesSize 相当于总容量,unoptimizedTotalClasses 相当于要占用的空间。
int namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
//gdb_objc_realized_classes表 与runtime_init 两张表的(unattachedCategories allocatedClasses)的区别是
//gdb_objc_realized_classes与allocatedClasses的区别是一个是总表,一个是已经创建的类的表。是包含关系。
gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
- 小对象类型的处理。
- 创建一张类的总表,这个表包含所有的类。
- 表的大小也遵循负载因子,这里
namedClassesSize = totalClasses * 4 / 3
相当于是负载因子3/4
的逆过程。namedClassesSize
相当于总容量,totalClasses
相当于要占用的空间。 -
gdb_objc_realized_classes
与runtime_init
中的unattachedCategories
与allocatedClasses
有点像。
gdb_objc_realized_classes定义
// This is a misnomer: gdb_objc_realized_classes is actually a list of
// named classes not in the dyld shared cache, whether realized or not.
// This list excludes lazily named classes, which have to be looked up
// using a getClass hook.
NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.h
allocatedClasses定义
/***********************************************************************
* allocatedClasses
* A table of all classes (and metaclasses) which have been allocated
* with objc_allocateClassPair.
**********************************************************************/
namespace objc {
static ExplicitInitDenseSet allocatedClasses;
}
-
gdb_objc_realized_classes
是一张总表,无论类是否实例化。 -
allocatedClasses
包含的是所有allocated
的类和元类。 - 所以
gdb_objc_realized_classes
应该包含allocatedClasses
。
doneOnce
的作用相当于是创建类的总表。
3.2 UnfixedSelectors
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) continue;
bool isBundle = hi->isBundle();
//从macho获取的
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
//从dyld中获取
SEL sel = sel_registerNameNoLock(name, isBundle);
if (sels[i] != sel) {
if (strcmp(name,"instanceMethod") == 0) {
printf("find instanceMethod");
}
sels[i] = sel;
}
}
}
}
-
sel
是在dyld
和llvm
的时候加载的。 -
sels[i]
是从macho
获取的。 -
sel
是由名称和地址组成的。这里赋值相当于是对地址的修复。
可以打个断点验证下:
发现确实是地址不一样,以
dyld
的地址为准。
3.3 discover classes
for (EACH_HEADER) {
……
//获取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) {
// 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;
}
}
}
断点调试进不到resolvedFutureClasses
的逻辑。resolvedFutureClasses
原始内存发生了移动,内存没有被清理掉(残留的类)。正常情况下不应该存在。所以核心就是readClass
了。
在readClass
前后分别打断点验证:
所以
readClass
的核心逻辑应该就是关联类的信息,对类进行了处理。
3.4 remap classes
if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
// fixme why doesn't test future1 catch the absence of this?
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
}
}
- 通过
noClassesRemapped
方法判断是否有类引用(_objc_classrefs)
需要进行重映射 - 核心逻辑是对类进行重新映射,读取的是
macho
中的数据__objc_classrefs
与__objc_superrefs
。最终调用remapClassRef
进行重新映射。
3.5 objc_msgSend_fixup
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
for (i = 0; i < count; i++) {
//修复旧的vtable。比如alloc的imp改为直接调用objc_alloc,而不是走alloc方法的实现。
fixupMessageRef(refs+i);
}
}
主要逻辑是修复sel
的调用,比如alloc
的imp
改为直接调用objc_alloc
,而不是走alloc
的实现。正常情况下不会走这个逻辑,在llvm
阶段已经处理了。
fixupMessageRef
的实现:
static void
fixupMessageRef(message_ref_t *msg)
{
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == @selector(alloc)) {
//alloc绑定得到objc_alloc
msg->imp = (IMP)&objc_alloc;
} else if (msg->sel == @selector(allocWithZone:)) {
msg->imp = (IMP)&objc_allocWithZone;
} else if (msg->sel == @selector(retain)) {
msg->imp = (IMP)&objc_retain;
} else if (msg->sel == @selector(release)) {
msg->imp = (IMP)&objc_release;
} else if (msg->sel == @selector(autorelease)) {
msg->imp = (IMP)&objc_autorelease;
} else {
msg->imp = &objc_msgSend_fixedup;
}
}
else if (msg->imp == &objc_msgSendSuper2_fixup) {
msg->imp = &objc_msgSendSuper2_fixedup;
}
else if (msg->imp == &objc_msgSend_stret_fixup) {
msg->imp = &objc_msgSend_stret_fixedup;
}
else if (msg->imp == &objc_msgSendSuper2_stret_fixup) {
msg->imp = &objc_msgSendSuper2_stret_fixedup;
}
#if defined(__i386__) || defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fpret_fixup) {
msg->imp = &objc_msgSend_fpret_fixedup;
}
#endif
#if defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fp2ret_fixup) {
msg->imp = &objc_msgSend_fp2ret_fixedup;
}
#endif
}
3.6 discover protocols
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
ASSERT(cls);
//创建表(只创建一次)
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->hasPreoptimizedProtocols();
……
bool isBundle = hi->isBundle();
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
-
protocol_map
是一个NXMapTable
是通过NXCreateMapTable
创建的。可以通过protocols()
获取。
按照官方的日志猜测应该与discover classes
的逻辑差不多,是对protocol
的地址与名称进行绑定。那么核心逻辑就在readProtocol
中了。自定义一个协议(需要类实现):
@protocol HPProtocol
- (void)protocolInstanceMethod;
+ (void)protocolClassMethod;
@end
在readProtocol
加入条件判断进行断点调试,发现进入了最后的else
分支,代码如下:
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 (strcmp(newproto->mangledName, "HPProtocol") == 0) {
printf("%s %s\n",__func__,newproto->mangledName);
}
//调试代码 结束
……
else {
// New protocol from an un-preoptimized image. Fix it up in place.
// fixme duplicate protocols from unloadable bundle
newproto->initIsa(protocol_class); // fixme pinned
insertFn(protocol_map, newproto->mangledName, newproto);
……
}
}
- 首先关联了
isa
指针,指向了Protocol
。Protocol
的isa
是一个纯指针。 - 核心逻辑就是
insertFn
方法了。在这里指向了NXMapInsert
。参数是protocol_map, newproto->mangledName, newproto
。这里与readClass
调用的同一个关联方法。
创建协议表,将协议与地址关联并加入表中。
3.7 fix up @protocol references
for (EACH_HEADER) {
if (launchTime && hi->isPreoptimized())
continue;
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}
重映射协议的引用,自定义的协议不会走到这里
3.8 discover categories
// Discover categories. Only do this after the initial category
// attachment has been done. For categories present at startup,
// discovery is deferred until the first load_images call after
// the call to _dyld_objc_notify_register completes. rdar://problem/53119145
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
注释已经说的很明白了,不会进入这个逻辑(即使实现了分类的+ load
也不会进入)。分类的加载必须在load_images
之后。至于具体怎么加载将在后续文章介绍。
3.9 realize non-lazy classes
非懒加载类的处理。
// +load handled by prepare_load_methods()
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
//从__objc_nlclslist获取 no-lazy classes
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
//重新映射类
Class cls = remapClass(classlist[i]);
if (!cls) continue;
//调试代码 开始
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "HPObject") == 0) {
printf("%s %s\n",__func__,mangledName);
}
printf("%s %s\n",__func__,mangledName);
//调试代码 结束
//加入allocatedClasses表中,已经加入不会再次加入。
addClassTableEntry(cls);
……
//初始化类
realizeClassWithoutSwift(cls, nil);
}
}
- 一般情况下自己实现的类是不会进入这个逻辑的(除非实现了
+ load
方法)。 - 根据注释可以看到只有非懒加载类会进入这个逻辑,
nlclslist
就是获取非懒加载类列表。通过macho
的__objc_nlclslist
获取。实现了+load
方法的类会出现在__objc_nlclslist
中。 - 核心就是
realizeClassWithoutSwift
的初始化逻辑了。这个方法在之前的消息慢速查找流程遇见过了。
非懒加载类分为三种情况:
1.自己实现了+ load
方法。(会出现在__objc_nlclslist
中)
2.子类实现了+ load
方法。(因为子类初始化会初始化父类,不会出现在__objc_nlclslist
中)
3.分类实现了+load
方法。(这里包括自己的分类以及子类的分类,不是在map_images
流程而是在prepare_load_methods
中实例化类的,不会出现在__objc_nlclslist
中)
⚠️这就说明了尽量避免在
+ load
方法中进行逻辑处理。整个过程是一个连锁反映。
添加+load
方法的类就会出现在__objc_nlclslist
中了:
为什么有懒加载和非懒加载类的区别?
为了按需分配,显然在启动过程中初始化的类越少启动速度就越快。
类的初始化核心逻辑在realizeClassWithoutSwift
中,这块逻辑将在后续文章中详细分析。
现在已经清楚了非懒加载类的实例化入口,那么懒加载类是在哪里实例化的呢?既然要实例化肯定要在调用realizeClassWithoutSwift
,在其中打个调试断点(记的去掉+ load
方法):
可以看到是在调用
alloc
的时候进行慢速消息查找的时候实例化的。那么这个是不是alloc
专属的呢?在alloc
源码中并没有相关痕迹。那就直接调用一个类方法,发现有同样的堆栈。所以就说明了在类进行第一次发送消息的时候进行的实例化。
- 对于非懒加载类实现了
+load
方法(子类/分类/自己),类就会提前加载,为+ load
的调用做准备。 - 对于懒加载类,是在第一次消息发送
objc_msgSend
,进行lookUpImpOrForward
消息慢速查找的时候进行初始化的。
懒加载和非懒加载对比:
- 懒加载类:数据加载推迟到第一次发送消息的时候。
lookUpImpOrForward
realizeClassMaybeSwiftMaybeRelock
realizeClassWithoutSwift
methodizeClass
- 非懒加载类:
map_images
的时候加载所有类数据。readClass
_getObjc2NonlazyClassList
realizeClassWithoutSwift
methodizeClass
3.10 realize future classes
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);
}
future
的处理,正常情况下是不会进入这个逻辑的。
四、readClass
通过上面的分析已经清楚了readClass
进行了地址关联到类的操作,那么具体是怎么操作的就需要看readClass
的实现了:
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
//调试判断逻辑
if (strcmp(mangledName, "HPObject") == 0) {
printf("%s HPObject\n",__func__);
}
//不会进入这里
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) {
//不会进入这里的逻辑,所以不是在这里进行的ro-rw操作。
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_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro();
memcpy(newCls, cls, sizeof(objc_class));
// Manually set address-discriminated ptrauthed fields
// so that newCls gets the correct signatures.
newCls->setSuperclass(cls->getSuperclass());
newCls->initIsa(cls->getIsa());
rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
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
//关联类信息,加入总表 gdb_objc_realized_classes
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.");
}
//将类加入allocatedClasses表中。
addClassTableEntry(cls);
}
// for future reference: shared cache never contains MH_BUNDLEs
if (headerIsBundle) {//正常不会进入这个逻辑
//设置标志位
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}
为了方便定位,以自己创建的类为例进行调试分析,readClass
加入以下逻辑判断:
if (strcmp(mangledName, "HPObject") == 0) {
printf("%s HPObject\n",__func__);
}
在newCls = popFutureNamedClass
分支中惊喜的发下了对ro
和rw
的操作,但是条件是future
并且断点也没有进入这个逻辑,那么核心逻辑就是:
{
if (mangledName) { //some Swift generic classes can lazily generate their names
//关联类信息,加入总表 gdb_objc_realized_classes
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.");
}
//将类加入allocatedClasses表中。
addClassTableEntry(cls);
}
正常情况下逻辑会进入addNamedClass
分支以及执行addClassTableEntry
。
addNamedClass的实现:
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked();
Class old;
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);
addNonMetaClass(cls);
} else {
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
}
- 核心逻辑是将
cls
加入gdb_objc_realized_classes
总表中。这张表示在doneOnce
中创建的。 -
addNamedClass
后类就与地址进行关联了。核心逻辑是NXMapInsert
处理的。也就是插入总表的时候进行的关联。以MapPair(key-value)
的形式进行关联。
typedef struct _MapPair {
const void *key;
const void *value;
} MapPair;
addClassTableEntry的实现:
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
//类插入 allocatedClasses 表中
set.insert(cls);
if (addMeta)
//元类插入 allocatedClasses 表中
addClassTableEntry(cls->ISA(), false);
}
- 将类和元类插入
allocatedClasses
表中。这张表是在runtime_init
中创建的。
readClass 的核心逻辑是将类与地址关联,并加入gdb_objc_realized_classes
与allocatedClasses
表中
总结
- _objc_init 核心逻辑
- 环境变量初始化(有3种方式进行查看:1.修改源码打印日志;2.终端
export OBJC_HELP=1
输出;3.源码文件查看objc-env.h
)。 - 自身
c++
构造函数的调用(比dyld
早,进行了section
数据的替换,dyld
不会调用到)。 -
runtime
相关表的初始化。 - 异常系统的初始化,可以对其进行注册进行异常的监控处理。
signal
的异常需要额外处理。 - 注册
map_images
、load_images
、unmap_image
的回调。
- 环境变量初始化(有3种方式进行查看:1.修改源码打印日志;2.终端
- map_images(核心是
read_images
)-
doneOnce
主要是创建类的总表。 -
UnfixedSelectors
修复预编译阶段的@selector
的混乱问题。 -
discover classes
关联类的信息。-
readClass
-
addNamedClass
:将类加入gdb_objc_realized_classes
表中(doneOnce
中创建)。在NXMapInsert
中进行了类名与地址的关联。 -
addClassTableEntry
:将类和元类插入allocatedClasses
表中(runtime_init
中创建)。
-
-
-
remap classes
修复重映射一些没有被镜像文件加载进来的类 。 -
objc_msgSend_fixup
修复一些消息。正常情况下llvm
已经处理了。 -
discover protocols
创建协议表,将协议与地址关联并加入表中(类需要实现协议)。 -
fix up @protocol references
修复没有被加载的协议 -
discover categories
分类处理,正常情况不会走到这里(即使实现了分类+ load
也不会进入)。分类的加载必须在load_images
之后 -
realize non-lazy classes
非懒加载类的处理。- 核心实现逻辑在
realizeClassWithoutSwift
中。- 对于非懒加载类实现了
+load
方法(子类/分类/自己),类就会提前加载(在read_images
中),为+ load
的调用做准备。 - 对于懒加载类,是在第一次消息发送
objc_msgSend
进行lookUpImpOrForward
消息慢速查找的时候进行初始化的。
- 对于非懒加载类实现了
- 非懒加载类的三种情况:尽量避免在
+load
中做逻辑处理,是一个连锁反应。- 自己实现了+ load方法。
- 子类实现了+ load方法。(因为子类初始化会初始化父类)
- 分类实现了+load方法。(这里包括自己的分类以及子类的分类)
- 核心实现逻辑在
-
realize future classes
没有被处理的类,优化那些被侵犯的类。
-