Objective-C 类的加载原理(上)

上篇文章中分析了dyld整个流程以及dyldobjc的交互。这篇文章将继续分析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调用之前,相当于objcc++构造函数是自己调用的,不是dyld调用的。
  • runtime_initruntime相关两张表的初始化。
  • exception_init:初始化 libobjc 的异常处理系统。
  • cache_t::init:缓存条件初始化。
  • _imp_implementationWithBlock_init:启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,会迫不及待地加载trampolines dylib
  • _dyld_objc_notify_register:注册map_imagesload_imagesunmap_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);
}

输出:


image.png

1.1.2 终端输出

export OBJC_HELP=1

➜  ~ export OBJC_HELP=1
➜  ~ cd Desktop/
image.png
  • 先导出OBJC_HELP,然后执行一个终端命令就会打印出环境变量了。

1.1.3 源码文件查看

objc源码objc-env.h文件中可以查看。探索过程可以参考OC对象isa部分的分析逻辑。

image.png

1.1.4 环境变量验证

在环境变量中有两个配置:OBJC_DISABLE_NONPOINTER_ISAOBJC_PRINT_LOAD_METHODS。直接在scheme->Arguments->Environment Variables中配置。

image.png

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调用之前,相当于objcc++构造函数是自己调用的,不是dyld调用的。因为dyld调用的时机太晚了,objc对这个section进行了替换,所以后续dyld`不会调用到这块。
  • 内部有两个逻辑一个是通过__mod_init_funcs,一个是通过__init_offsetsmacho文件读取c++构造函数。目前暂不清楚什么情况下会走__init_offsets的逻辑。

直接在源码中添加一个c++构造函数进行验证:

image.png

上面对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_funcSECTION_TYPES_MOD_INIT_FUNC_POINTERS(0x9)
  • __init_offsets被修改称为__objc_init_offsSECTION_TYPES_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。这个也就是TerminatorsSECTION_TYPES_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运行时环境初始化,主要是unattachedCategoriesallocatedClasses 两张表的初始化。

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
  • 通过NSGetUncaughtExceptionHandlerNSSetUncaughtExceptionHandler进行异常回调的注册,NSGetUncaughtExceptionHandler保留之前的回调(其它SDK可能实现了)。要自己处理完后回调给对方。还可以对NSSetUncaughtExceptionHandler函数进行hook,自己处理完后再调用原始调用。
  • 在处理逻辑中可以先进行本地的保存,然后做相关的提示(需要自己创建runloop),最后需要移除注册的回调。
  • 这样详细信息就可以获取到了。

注意点:

  • 只能捕获OC抛出的异常,signal无法捕获,signal异常可以设置signalhandler
  • 自己的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);
    }
}

sigactionsignal函数的区别:

  • 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逻辑。这样就能断住断点了:

image.png

最终crash信息会保存在Caches文件中:

image.png

完整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_imagesload_imagesunmap_image的回调。实现在dyld中。

  • map_images:管理文件中和动态库中所有的符号 (classProtocolselectorcategory)。
  • load_images:加载执行load方法
  • unmap_image:释放类相关资源。

这里有个疑问,在dyldmap_imagesload_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的核心逻辑是要找镜像文件是怎么被加载的,也就是对classProtocolselectorcategory等相关的操作。在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代码折叠起来后会有惊喜:

image.png

根据官方的log信息可以大概了解每个代码块的内容。(这里为了方便截图,我拷贝了_read_images方法删除了注释进行了精简。)
主要内容如下:

  • 1.条件控制进行一次的加载。(doneOnce
  • 2.修复预编译阶段的 @selector 的混乱问题。
  • 3.错误混乱的类处理 。
  • 4.修复重映射一些没有被镜像文件加载进来的类 。
  • 5.修复一些消息。
  • 6.当我们类里面有协议的时候 :readProtocol
  • 7.修复没有被加载的协议。
  • 8.分类处理。
  • 9.类的加载处理。
  • 10.没有被处理的类的优化。

显然89是核心。也就是load_categories_nolockrealizeClassWithoutSwift

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_classesruntime_init中的unattachedCategoriesallocatedClasses有点像。

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是在dyldllvm的时候加载的。
  • sels[i]是从macho获取的。
  • sel是由名称和地址组成的。这里赋值相当于是对地址的修复。

可以打个断点验证下:

image.png

发现确实是地址不一样,以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前后分别打断点验证:

image.png

所以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的调用,比如allocimp改为直接调用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指针,指向了ProtocolProtocolisa是一个纯指针。
  • 核心逻辑就是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中了:

image.png

为什么有懒加载和非懒加载类的区别?
为了按需分配,显然在启动过程中初始化的类越少启动速度就越快。

类的初始化核心逻辑在realizeClassWithoutSwift中,这块逻辑将在后续文章中详细分析。

现在已经清楚了非懒加载类的实例化入口,那么懒加载类是在哪里实例化的呢?既然要实例化肯定要在调用realizeClassWithoutSwift,在其中打个调试断点(记的去掉+ load方法):

image.png

可以看到是在调用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分支中惊喜的发下了对rorw的操作,但是条件是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_classesallocatedClasses表中

总结

  • _objc_init 核心逻辑
    • 环境变量初始化(有3种方式进行查看:1.修改源码打印日志;2.终端export OBJC_HELP=1输出;3.源码文件查看objc-env.h)。
    • 自身c++构造函数的调用(比dyld早,进行了section数据的替换,dyld不会调用到)。
    • runtime相关表的初始化。
    • 异常系统的初始化,可以对其进行注册进行异常的监控处理。signal的异常需要额外处理。
    • 注册map_imagesload_imagesunmap_image的回调。
  • 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没有被处理的类,优化那些被侵犯的类。

你可能感兴趣的:(Objective-C 类的加载原理(上))