美文网首页iOS底层
Objective-C 类的加载原理(上)

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

作者: HotPotCat | 来源:发表于2021-07-13 22:55 被阅读0次

    上篇文章中分析了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 <typename P>
    void dosect(uint8_t *start, macho_section<P> *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 <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*/
    

    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 <typename Type>
    class ExplicitInit {
        alignas(Type) uint8_t _storage[sizeof(Type)];
    
    public:
        template <typename... Ts>
        void init(Ts &&... Args) {
            new (_storage) Type(std::forward<Ts>(Args)...);
        }
    
        Type &get() {
            return *reinterpret_cast<Type *>(_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 <libkern/OSAtomic.h>
    #include <execinfo.h>
    #include <stdatomic.h>
    
    //异常名称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<UITouch *> *)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<Class> 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 <NSObject>
    
    - (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 类的加载原理(上)

        本文链接:https://www.haomeiwen.com/subject/rixqpltx.html