OC底层原理-objc 818(八)类的加载原理-dyld&_objc_init

544 阅读8分钟

程序的加载原理

代码的编译过程

我们编写完成代码是需要通过编译器来完成编译后,才能变成可以执行的文件,也就是我们通常说的可执行文件。

那么编译过程是怎样的呢,下面我们来通过流程图为大家分析一下:

程序的执行,就是把可执行的文件加载到内存中进行执行。我们将可执行文件叫做Mach-O,Mach-O的运行需要依赖运行库(.a .lib .so),运行库分为动态库和静态库,这些库是可执行的二进制文件,能够被加载倒内存中。

静态库和动态库

  • 静态库:例如.a和.framework。静态库链接时,会被完整地复制到可执行文件中,被使用到了多次时,就会被复制到内存中多次,这样就会产生拷贝冗余,造成内存浪费。
  • 动态库:例如.lib和.framework。动态库链接时,只会存在一份,不会复制多份。动态库在内存中是共享的,系统只加载一次,加载完成后谁用就会去引用这块内存,这样就既节省了时间又省了内存。
  • 静态库就是我们常说的值拷贝,而动态库就是我们常说的地址拷贝。
  • 图解

  • 动态链接库加载倒内存的过程就是由dyld(dynamic link editor)动态链接器完成的。

dyld动态连接器

  • dyld的介绍:dyld是iOS操作系统的一个重要组成部分,再系统内核做好程序准备工作之后,会交由dyld负责余下的工作。
  • dyld的作用:加载各个库,也就是image镜像文件,由dyld从内存中读到列表中,加载主程序,link链接各个动静态库,进行主程序的初始化工作。
  • dyld工作流程图如下,图中简单描述了动态库的注册和动态库的加载过程,具体的分析还需要看底层的源码,此处暂时不做深入探究。

_objc_init与dyld

_objc_init源码

首先我们再objc源码中全局搜索_objc_init,在objc_os.mm文件下可以看到实现源码如下:

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    // 读取影响运行时的环境变量
    // 可以再Edite Scheme中的Environment Variable中进行配置,来打印环境变量帮助
    environ_init();
    // 关于线程key的绑定
    tls_init();
    // 运行C++静态构造函数 在dyld调用我们的静态构造函数之前,libc会调用objc_init,因此我们必须自己做
    static_init();
    // runtime运行时环境变量初始化
    runtime_init();
    // 初始化libobjc的异常处理方法
    exception_init();
#if __OBJC2__
		// 缓存条件初始化
    cache_t::init();
#endif
		// 启动回调机制
    _imp_implementationWithBlock_init();
		// 注册dyld
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

根据源码所知,主要分为一下几部分:

  • environ_init:初始化一系列的环境变量,病读取影响运行的环境变量
  • tls_init:关于线程key的绑定
  • static_init:运行C++静态构造函数(只会运行系统级别的构造函数),在dyld调用静态析构函数之前,libc会调用_objc_init
  • runtime_init:runtime运行时环境初始化,里面操作是unattachedCategories、allocatedClasses表的初始化
  • exception_init:初始化libobjc的异常处理系统
  • cache_t::init:cache缓存初始化
  • _imp_implementationWithBlock_init:启动回调机制,通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
  • _dyld_objc_notify_register:dyld的注册
    • 仅供objc运行时使用。注册应用程序,以便再映射、取消映射和初始化objc镜像文件时使用,dyld将使用包含objc_image_info的镜像文件数组,回调mapped函数。
    • map_images:dyld将image镜像文件加载进内存时,会触发该函数
    • load_image:dyld初始化image会触发该函数
    • unmap_image:dyld将image移除时会触发该函数

environ_init方法:环境变量初始化

environ_init源码如下,我截取了比较关键的部分,此处主要循环打印环境变量信息

void environ_init(void) 
{
    // ……省略
    // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    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);
        }
    }
}

环境变量打印

方式一:修改源码的方式进行打印

  • 首先我们移除掉内层和外层的if条件,只保留上述代码块;
  • 运行我们的objc源码项目;
  • 查看控制台打印结果,会打印出所有的环境变量信息

方式二:通过终端命令打印环境变量

需要再终端中输入export OBJC_HELP=1指令,打印环境变量

方式三:打印指定的环境变量信息

设置方式:

可以通过target -- Edit Scheme -- Run --Arguments -- Environment Variables配置,其中常用的环境变量主要有以下几个:

  • DYLD_PRINT_STATISTICS设置DYLD_PRINT_STATISTICS为YES,控制台就会打印APP的加载时长,包括整体加载时长和动态库加载时长,即main函数之前dyld的处理时间,可以通过设置了解耗时部分,并对其进行启动优化
  • OBJC_DISABLE_NONPOINTER_ISA:杜绝生成相应的nonpointer isa制(nonpointer isa 优化后的isa,指针地址未优化的isa末尾为1,优化后的isa末尾为0),生成的都是普通的isa
  • OBJC_PRINT_LOAD_METHODS:打印Class即Category的load方法的调用信息
  • NSDoubleLocalizedStrings:项目国际化本地化的时候时一个耗时的工作,想要检测国际化翻译好的文字UI会变成什么样子,可以指定这个启动项,可以设置NSDoubleLocalizedStrings为YES
  • NSShowNonLocalizedStrings:在完成国际化的时候,偶尔会有一些字符串没有做本地化,这时就可以设置NSShowNonLocalizedStrings为YES,所有没有本地化的字符串全都会变成大写。

OBJC_DISABLE_NONPOINTER_ISA

OBJC_DISABLE_NONPOINTER_ISA为例,将其设置为YES

未设置OBJC_DISABLE_NONPOINTER_ISA时打印objc对象,isa地址的二进制末尾为1

设置OBJC_DISABLE_NONPOINTER_ISA时打印机objc对象,isa地址的二进制末尾为0

所以OBJC_DISABLE_NONPOINTER_ISA可以控制isa开关,从而优化整个内存结构

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
}

static_init:运行系统级别的C++静态构造函数

主要是运行系统级别的C++静态构造函数,在dyld调用我们的静态构造函数之前,libc调用_objc_init方法,即系统级别的C++构造函数,自定义的C++构造函数运行,源码:

static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        UnsignedInitializer init(offsets[i]);
        init();
    }
}

runtime_init:运行时环境初始化

主要是运行时的初始化,主要分为两部分:分类初始化、类的表初始化,源码:

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}

exception_init:初始化libobjc的异常处理系统

主要是初始化libobjc的异常处理系统,注册异常处理的回调,从而监控异常的处理,源码:

void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
  • 当有crash发生时,就会到old_terminate方法,最终走到uncaught_handler进行异常抛出
    • crash:是指系统发生了一下不被允许的指令,然后系统会发出的一个信号
  • old_terminate源码
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)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // It's not an objc object. Continue to C++ terminate.
            (*old_terminate)();
        }
    }
}
  • 搜索uncaught_handler,我们发现是一个全局静态变量,是在应用层通过objc_setUncaughtExceptionHandler传入一个异常处理的回调方法,并在该方法内将异常处理方法赋值给uncaught_handler
// uncaught_handler初始
static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;

// APP传入处理异常用的回调方法
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}

cache_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
}

_imp_implementationWithBlock_init:启动回调机制

该方法主要是启动回调机制,通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载libobjc-trampolines.dylib,源码

_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
    // Eagerly load libobjc-trampolines.dylib in certain processes. Some
    // programs (most notably QtWebEngineProcess used by older versions of
    // embedded Chromium) enable a highly restrictive sandbox profile which
    // blocks access to that dylib. If anything calls
    // imp_implementationWithBlock (as AppKit has started doing) then we'll
    // crash trying to load it. Loading it here sets it up before the sandbox
    // profile is enabled and blocks it.
    //
    // This fixes EA Origin (rdar://problem/50813789)
    // and Steam (rdar://problem/55286131)
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") == 0 ||
         strcmp(__progname, "Steam Helper") == 0)) {
        Trampolines.Initialize();
    }
#endif
}

_dyld_objc_notify_register:dyld注册

_dyld_objc_notify_register方法

这个方法是dyld的注册方法,其源码是在dyld源码中,下面是_dyld_objc_notify_register的声明

//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded.  During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images.  During any later dlopen() call,
// dyld will also call the "mapped" function.  Dyld will call the "init" function when dyld would be called
// initializers in that image.  This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);

通过注释可以分析出:

  • 仅运行objc运行时使用
  • 注册处理程序,以便在镜像映射和取消映射和初始化objc镜像是调用
  • dyld将会通过一个包含objc-image-info的镜像文件的数组回调mapped函数

_dyld_objc_notify_register调用

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

方法中的三个参数含义:

  • &map_images:dyld将image镜像文件加载倒内存中,会触发该函数
  • load_image:dyld初始化image会触发该函数
  • unmap_image:dyld将image移除时,会触发该函数

load_image方法其实就是调用load方法,map_image方法,&map_image是指针传递,指向是同一块实现的地址,如果有什么变化就可以第一时间知道。

接下来我们就研究一下map_images

map_images

map_images源码

首先,我们先来看一下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方法,下面我们看看map_images_nolock源码

map_images_nolock源码

map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

   	// …………省略

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // Call image load funcs after everything is set up.
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }
}

map_images_nolock中的关键就在read_image方法,加下来我们就来完整的看一下read_image具体做了什么

read_image

read_image源码

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t i;
    Class *resolvedFutureClasses = nil;
    size_t resolvedFutureClassCount = 0;
    static bool doneOnce;
    bool launchTime = NO;
    TimeLogger ts(PrintImageTimes);

    runtimeLock.assertLocked();

    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++
		
    // 条件控制进行一次加载
    if (!doneOnce) {……}

    // Fix up @selector references
    // 修复预编译阶段的@selector的混乱问题
    // 就是不同类中有相同方法 但是相同方法的地址是不一样的
    static size_t UnfixedSelectors;
    {……}

    ts.log("IMAGE TIMES: fix up selector references");

    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    // 错误混乱的类处理
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    for (EACH_HEADER) {……}

    ts.log("IMAGE TIMES: discover classes");

    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.
   	// 修复重映射一些没有被镜像文件加载进来的类
    if (!noClassesRemapped()) {……}

    ts.log("IMAGE TIMES: remap classes");

#if SUPPORT_FIXUP
    // Fix up old objc_msgSend_fixup call sites
    // 修复一些消息
    for (EACH_HEADER) {……}

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif

    // Discover protocols. Fix up protocol refs.
    // 当类中有协议时 readProtocol
    for (EACH_HEADER) {……}

    ts.log("IMAGE TIMES: discover protocols");

    // Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    // 修复没有被加载的协议
    for (EACH_HEADER) {……}

    ts.log("IMAGE TIMES: fix up @protocol references");

    // 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);
        }
    }

    ts.log("IMAGE TIMES: discover categories");

    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code before
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()

    // Realize non-lazy classes (for +load methods and static instances)
    // 类的加载处理
    for (EACH_HEADER) {……}

    ts.log("IMAGE TIMES: realize non-lazy classes");

    // Realize newly-resolved future classes, in case CF manipulates them
    // 没有被处理的类 优化那些被侵犯的类
    if (resolvedFutureClasses) {……}

    ts.log("IMAGE TIMES: realize future classes");
#undef EACH_HEADER
}

从整体上可以看出read_image是对一些log日志的打印输出,主要如下:

  • 条件控制运行一次加载
  • 修复预编译阶段的@selector的混乱的问题
  • 错误混乱的类处理
  • 修复重映射一些没有被镜像文件加载进来的类
  • 修复一些消息
  • 当类中有协议时 readProtocol
  • 修复没有被加载的协议
  • 分类的处理
  • 类的加载处理
  • 没有被处理的类,优化那些被侵犯的类

下面我来重点分析几个比较重要的逻辑:

doneOnce

源码

if (!doneOnce) {
    doneOnce = YES;
    launchTime = YES;

    // ……省略
    
    // namedClasses
    // Preoptimized classes don't go in this table.
    // 4/3 is NXMapTable's load factor
    int namedClassesSize = 
        (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
    // 创建哈希表 存放所有类
    gdb_objc_realized_classes =
        NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

    ts.log("IMAGE TIMES: first time tasks");
}

加载一次下次就不会再次进入判断。只有第一次进来时创建表gdb_objc_realized_classes,表里存放所有的类(不管是实现的还是未实现的都存放在里面),是一张存放类的总表

UnfixedSelectors

源码

// Fix up @selector references
static size_t UnfixedSelectors;
{
    mutex_locker_t lock(selLock);
    for (EACH_HEADER) {
        if (hi->hasPreoptimizedSelectors()) continue;

        bool isBundle = hi->isBundle();
        // 从Mach-O中获取方法列表
        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) {
                sels[i] = sel;
            }
        }
    }
}

不同类中可能存在相同的方法,但是相同的方法地址是不同的,dyld的方法是准确的,所以此处需要对Mach-O的方法进行纠正。

下面我们通过debug断电来打印一下。

sels是通过_getObjc2SelectorRefs获取Mach-O中的方法信息,Mach-O有相对位移地址偏移地址

sel是通过sel_registerNameNoLock获取的dyld的方法信息,dyld是链接整个程序的,所以dyld是最准确的。因为方法是存放在类中,没给类中的位置是不一样的,所以方法的地址也就不一样,那么久必须对那些混乱的方法进行修复处理。

总结: Mach-O中存储的是对当前类的相对地址,dyld是加载过程中内存中的实际地址,所以需要将dyld的imp为准进行纠正。

错误混乱的类处理

源码

for (EACH_HEADER) {
    if (! mustReadClasses(hi, hasDyldRoots)) {
        // Image is sufficiently optimized that we need not call readClass()
        continue;
    }
		
    // 从macho中读取类列表信息
    classref_t const *classlist = _getObjc2ClassList(hi, &count);

    bool headerIsBundle = hi->isBundle();
    bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

    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;
        }
    }
}
  • cls:是我们从macho中读取出来的,指向的是一块内存地址。
  • newCls:是我们通过readClass获取的,但是newCls有特殊性,在我们未调用readClass为其赋值时,系统会给newClas分配一块脏地址,一旦调用了readClass后我们会对其重新赋值,并且会为他设置名称,我们我对象的名称也就是在这里进行设置的。
  • 下面我们通过代码来调试一下

通过上述调试,也证实了readClass的作用就是把类名和地址关联起来。

readClass

源码

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
		// 获取类名
    const char *mangledName = cls->nonlazyMangledName();
    
    if (missingWeakSuperclass(cls)) {……}
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (mangledName != nullptr) {……}
    
    if (headerIsPreoptimized  &&  !replacing) {……
    } else {
        if (mangledName) { //some Swift generic classes can lazily generate their names
       			// 将类名和地址关联起来
            addNamedClass(cls, mangledName, replacing);
        } else {
            Class meta = cls->ISA();
            const class_ro_t *metaRO = meta->bits.safe_ro();
            ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
            ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
        }
        // 将关联好的类插入到另一张哈希表中 初始化完成的类列表
        addClassTableEntry(cls);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {……}
    
    return cls;
}

关键方法:

  • nonlazyMangledName获取类名
  • addNamedClass将类名和地址关联起来
  • addClassTableEntry将关联好的类插入到另一张哈希表中,这张表中都是已经初始化完成的类

下面我们通过对cls进行过滤,研究我们自己创建的Person类

此过程通过nonlazyMangledName获取到了类的名称

nonlazyMangledName源码:

const char *nonlazyMangledName() const {
    return bits.safe_ro()->getName();
}

safe_ro源码:

const class_ro_t *safe_ro() const {
    class_rw_t *maybe_rw = data();
    if (maybe_rw->flags & RW_REALIZED) {
        // maybe_rw is rw
        return maybe_rw->ro();
    } else {
        // maybe_rw is actually ro
        return (class_ro_t *)maybe_rw;
    }
}

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);

        // getMaybeUnrealizedNonMetaClass uses name lookups.
        // Classes not found by name lookup must be in the
        // secondary meta->nonmeta table.
        addNonMetaClass(cls);
    } else {
    		// 更新存储所有类的哈希表 key是name value是cls
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
    ASSERT(!(cls->data()->flags & RO_META));

    // wrong: constructed classes are already realized when they get here
    // ASSERT(!cls->isRealized());
}

通过NXMapInsert方法更新gdb_objc_realized_classes哈希表,key是name,value是cls

NXMapInsert源码:

void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
    MapPair	*pairs = (MapPair *)table->buckets;
    unsigned	index = bucketOf(table, key);
    MapPair	*pair = pairs + index;
    if (key == NX_MAPNOTAKEY) {
	_objc_inform("*** NXMapInsert: invalid key: -1\n");
	return NULL;
    }

    unsigned numBuckets = table->nbBucketsMinusOne + 1;

    if (pair->key == NX_MAPNOTAKEY) {
	pair->key = key; pair->value = value;
	table->count++;
	if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
	return NULL;
    }
  
 	// …………省略
}

addClassTableEntry源码

static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();

    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    // allocatedClasses就是_objc_alloc runtime_init中初始化的
    auto &set = objc::allocatedClasses.get();

    ASSERT(set.find(cls) == set.end());

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

allocatedClasses_objc_init中进行初始化,主要是unattachedCategories(分类)和allocatedClasses(类)两张表,此时addClassTableEntry的操作是插入到allocatedClasses表中。同时还对元类进行了相应的处理。

通过对源码的分析和断电调试,我们发现rwro的获取和赋值并不是在readClass里面,那么重新回到_read_image继续向下看。

最终我们断电继续向下走,走到了类加载的区域发现,调用了realizeClassWithoutSwift方法,先透露一下,rw和ro的查找和赋值就是在这个方法中完成的,这个方法我们下节进行探讨