程序的加载原理

代码的编译过程

我们编写完成代码是需要通过编译器来完成编译后,才能变成可以执行的文件,也就是我们通常说的可执行文件。
那么编译过程是怎样的呢,下面我们来通过流程图为大家分析一下:
OC底层原理-objc 818(八)类的加载原理-dyld&_objc_init - 图1
程序的执行,就是把可执行的文件加载到内存中进行执行。我们将可执行文件叫做Mach-O,Mach-O的运行需要依赖运行库(.a .lib .so),运行库分为动态库和静态库,这些库是可执行的二进制文件,能够被加载倒内存中。

静态库和动态库

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

image.png

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

    dyld动态连接器

  • dyld的介绍:dyld是iOS操作系统的一个重要组成部分,再系统内核做好程序准备工作之后,会交由dyld负责余下的工作。

  • dyld的作用:加载各个库,也就是image镜像文件,由dyld从内存中读到列表中,加载主程序,link链接各个动静态库,进行主程序的初始化工作。
  • dyld工作流程图如下,图中简单描述了动态库的注册和动态库的加载过程,具体的分析还需要看底层的源码,此处暂时不做深入探究。

image.png

_objc_init与dyld

_objc_init源码

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

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

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

  • 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源码如下,我截取了比较关键的部分,此处主要循环打印环境变量信息

      1. void environ_init(void)
      2. {
      3. // ……省略
      4. // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
      5. if (PrintHelp || PrintOptions) {
      6. // …… 省略
      7. // 核心代码
      8. for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
      9. const option_t *opt = &Settings[i];
      10. if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
      11. if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
      12. }
      13. }
      14. }

      环境变量打印

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

      image.png

  • 首先我们移除掉内层和外层的if条件,只保留上述代码块;

  • 运行我们的objc源码项目;
  • 查看控制台打印结果,会打印出所有的环境变量信息
  • image.png

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

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

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

    设置方式:
    可以通过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
    image.png
    未设置OBJC_DISABLE_NONPOINTER_ISA时打印objc对象,isa地址的二进制末尾为1
    image.png
    设置OBJC_DISABLE_NONPOINTER_ISA时打印机objc对象,isa地址的二进制末尾为0
    image.png
    所以OBJC_DISABLE_NONPOINTER_ISA可以控制isa开关,从而优化整个内存结构

    tls_init:线程key的绑定

    主要是本地线程池的初始化以及析构,源码:
    1. void tls_init(void)
    2. {
    3. #if SUPPORT_DIRECT_THREAD_KEYS
    4. pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
    5. #else
    6. _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
    7. #endif
    8. }

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

    主要是运行系统级别的C++静态构造函数,在dyld调用我们的静态构造函数之前,libc调用_objc_init方法,即系统级别的C++构造函数,自定义的C++构造函数运行,源码:
    1. static void static_init()
    2. {
    3. size_t count;
    4. auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    5. for (size_t i = 0; i < count; i++) {
    6. inits[i]();
    7. }
    8. auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    9. for (size_t i = 0; i < count; i++) {
    10. UnsignedInitializer init(offsets[i]);
    11. init();
    12. }
    13. }

    runtime_init:运行时环境初始化

    主要是运行时的初始化,主要分为两部分:分类初始化、类的表初始化,源码:
    1. void runtime_init(void)
    2. {
    3. objc::unattachedCategories.init(32);
    4. objc::allocatedClasses.init();
    5. }

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

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

void exception_init(void) { old_terminate = std::set_terminate(&_objc_terminate); }

  1. - 当有crash发生时,就会到old_terminate方法,最终走到uncaught_handler进行异常抛出
  2. - crash:是指系统发生了一下不被允许的指令,然后系统会发出的一个信号
  3. - old_terminate源码
  4. ```objectivec
  5. static void (*old_terminate)(void) = nil;
  6. static void _objc_terminate(void)
  7. {
  8. if (PrintExceptions) {
  9. _objc_inform("EXCEPTIONS: terminating");
  10. }
  11. if (! __cxa_current_exception_type()) {
  12. // No current exception.
  13. (*old_terminate)();
  14. }
  15. else {
  16. // There is a current exception. Check if it's an objc exception.
  17. @try {
  18. __cxa_rethrow();
  19. } @catch (id e) {
  20. // It's an objc object. Call Foundation's handler, if any.
  21. (*uncaught_handler)((id)e);
  22. (*old_terminate)();
  23. } @catch (...) {
  24. // It's not an objc object. Continue to C++ terminate.
  25. (*old_terminate)();
  26. }
  27. }
  28. }
  • 搜索uncaught_handler,我们发现是一个全局静态变量,是在应用层通过objc_setUncaughtExceptionHandler传入一个异常处理的回调方法,并在该方法内将异常处理方法赋值给uncaught_handler。 ```objectivec

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

  1. <a name="COqJj"></a>
  2. ### cache_init:缓存初始化
  3. 主要是缓存初始化,源码:
  4. ```objectivec
  5. void cache_t::init()
  6. {
  7. #if HAVE_TASK_RESTARTABLE_RANGES
  8. mach_msg_type_number_t count = 0;
  9. kern_return_t kr;
  10. while (objc_restartableRanges[count].location) {
  11. count++;
  12. }
  13. kr = task_restartable_ranges_register(mach_task_self(),
  14. objc_restartableRanges, count);
  15. if (kr == KERN_SUCCESS) return;
  16. _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
  17. kr, mach_error_string(kr));
  18. #endif // HAVE_TASK_RESTARTABLE_RANGES
  19. }

_imp_implementationWithBlock_init:启动回调机制

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

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

_dyld_objc_notify_register:dyld注册

_dyld_objc_notify_register方法

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

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

通过注释可以分析出:

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

    _dyld_objc_notify_register调用

    1. _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的源码

  1. void
  2. map_images(unsigned count, const char * const paths[],
  3. const struct mach_header * const mhdrs[])
  4. {
  5. mutex_locker_t lock(runtimeLock);
  6. return map_images_nolock(count, paths, mhdrs);
  7. }

我们可以看到map_images方法内调用了map_images_nolock方法,下面我们看看map_images_nolock源码

map_images_nolock源码

  1. map_images_nolock(unsigned mhCount, const char * const mhPaths[],
  2. const struct mach_header * const mhdrs[])
  3. {
  4. static bool firstTime = YES;
  5. header_info *hList[mhCount];
  6. uint32_t hCount;
  7. size_t selrefCount = 0;
  8. // …………省略
  9. if (hCount > 0) {
  10. _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
  11. }
  12. firstTime = NO;
  13. // Call image load funcs after everything is set up.
  14. for (auto func : loadImageFuncs) {
  15. for (uint32_t i = 0; i < mhCount; i++) {
  16. func(mhdrs[i]);
  17. }
  18. }
  19. }

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

read_image

read_image源码

  1. void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
  2. {
  3. header_info *hi;
  4. uint32_t hIndex;
  5. size_t count;
  6. size_t i;
  7. Class *resolvedFutureClasses = nil;
  8. size_t resolvedFutureClassCount = 0;
  9. static bool doneOnce;
  10. bool launchTime = NO;
  11. TimeLogger ts(PrintImageTimes);
  12. runtimeLock.assertLocked();
  13. hIndex = 0; \
  14. hIndex < hCount && (hi = hList[hIndex]); \
  15. hIndex++
  16. // 条件控制进行一次加载
  17. if (!doneOnce) {……}
  18. // Fix up @selector references
  19. // 修复预编译阶段的@selector的混乱问题
  20. // 就是不同类中有相同方法 但是相同方法的地址是不一样的
  21. static size_t UnfixedSelectors;
  22. {……}
  23. ts.log("IMAGE TIMES: fix up selector references");
  24. // Discover classes. Fix up unresolved future classes. Mark bundle classes.
  25. // 错误混乱的类处理
  26. bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
  27. for (EACH_HEADER) {……}
  28. ts.log("IMAGE TIMES: discover classes");
  29. // Fix up remapped classes
  30. // Class list and nonlazy class list remain unremapped.
  31. // Class refs and super refs are remapped for message dispatching.
  32. // 修复重映射一些没有被镜像文件加载进来的类
  33. if (!noClassesRemapped()) {……}
  34. ts.log("IMAGE TIMES: remap classes");
  35. #if SUPPORT_FIXUP
  36. // Fix up old objc_msgSend_fixup call sites
  37. // 修复一些消息
  38. for (EACH_HEADER) {……}
  39. ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
  40. #endif
  41. // Discover protocols. Fix up protocol refs.
  42. // 当类中有协议时 readProtocol
  43. for (EACH_HEADER) {……}
  44. ts.log("IMAGE TIMES: discover protocols");
  45. // Fix up @protocol references
  46. // Preoptimized images may have the right
  47. // answer already but we don't know for sure.
  48. // 修复没有被加载的协议
  49. for (EACH_HEADER) {……}
  50. ts.log("IMAGE TIMES: fix up @protocol references");
  51. // Discover categories. Only do this after the initial category
  52. // attachment has been done. For categories present at startup,
  53. // discovery is deferred until the first load_images call after
  54. // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
  55. // 分类的处理
  56. if (didInitialAttachCategories) {
  57. for (EACH_HEADER) {
  58. load_categories_nolock(hi);
  59. }
  60. }
  61. ts.log("IMAGE TIMES: discover categories");
  62. // Category discovery MUST BE Late to avoid potential races
  63. // when other threads call the new category code before
  64. // this thread finishes its fixups.
  65. // +load handled by prepare_load_methods()
  66. // Realize non-lazy classes (for +load methods and static instances)
  67. // 类的加载处理
  68. for (EACH_HEADER) {……}
  69. ts.log("IMAGE TIMES: realize non-lazy classes");
  70. // Realize newly-resolved future classes, in case CF manipulates them
  71. // 没有被处理的类 优化那些被侵犯的类
  72. if (resolvedFutureClasses) {……}
  73. ts.log("IMAGE TIMES: realize future classes");
  74. #undef EACH_HEADER
  75. }

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

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

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

doneOnce

源码

  1. if (!doneOnce) {
  2. doneOnce = YES;
  3. launchTime = YES;
  4. // ……省略
  5. // namedClasses
  6. // Preoptimized classes don't go in this table.
  7. // 4/3 is NXMapTable's load factor
  8. int namedClassesSize =
  9. (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
  10. // 创建哈希表 存放所有类
  11. gdb_objc_realized_classes =
  12. NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
  13. ts.log("IMAGE TIMES: first time tasks");
  14. }

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

UnfixedSelectors

源码

  1. // Fix up @selector references
  2. static size_t UnfixedSelectors;
  3. {
  4. mutex_locker_t lock(selLock);
  5. for (EACH_HEADER) {
  6. if (hi->hasPreoptimizedSelectors()) continue;
  7. bool isBundle = hi->isBundle();
  8. // 从Mach-O中获取方法列表
  9. SEL *sels = _getObjc2SelectorRefs(hi, &count);
  10. UnfixedSelectors += count;
  11. for (i = 0; i < count; i++) {
  12. const char *name = sel_cname(sels[i]);
  13. // 从dyld中获取方法
  14. SEL sel = sel_registerNameNoLock(name, isBundle);
  15. if (sels[i] != sel) {
  16. sels[i] = sel;
  17. }
  18. }
  19. }
  20. }

不同类中可能存在相同的方法,但是相同的方法地址是不同的,dyld的方法是准确的,所以此处需要对Mach-O的方法进行纠正。
下面我们通过debug断电来打印一下。
image.png
sels是通过_getObjc2SelectorRefs获取Mach-O中的方法信息,Mach-O有相对位移地址偏移地址。
sel是通过sel_registerNameNoLock获取的dyld的方法信息,dyld是链接整个程序的,所以dyld是最准确的。因为方法是存放在类中,没给类中的位置是不一样的,所以方法的地址也就不一样,那么久必须对那些混乱的方法进行修复处理。
总结:
Mach-O中存储的是对当前类的相对地址,dyld是加载过程中内存中的实际地址,所以需要将dyld的imp为准进行纠正。

错误混乱的类处理

源码

  1. for (EACH_HEADER) {
  2. if (! mustReadClasses(hi, hasDyldRoots)) {
  3. // Image is sufficiently optimized that we need not call readClass()
  4. continue;
  5. }
  6. // 从macho中读取类列表信息
  7. classref_t const *classlist = _getObjc2ClassList(hi, &count);
  8. bool headerIsBundle = hi->isBundle();
  9. bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
  10. for (i = 0; i < count; i++) {
  11. Class cls = (Class)classlist[i];
  12. Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
  13. // 类信息发生错乱,类运行时可能发生移动,但是没有被删除,就是我们常说的野指针
  14. if (newCls != cls && newCls) {
  15. // Class was moved but not deleted. Currently this occurs
  16. // only when the new class resolved a future class.
  17. // Non-lazily realize the class below.
  18. resolvedFutureClasses = (Class *)
  19. realloc(resolvedFutureClasses,
  20. (resolvedFutureClassCount+1) * sizeof(Class));
  21. resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
  22. }
  23. }
  24. }
  • cls:是我们从macho中读取出来的,指向的是一块内存地址。
  • newCls:是我们通过readClass获取的,但是newCls有特殊性,在我们未调用readClass为其赋值时,系统会给newClas分配一块脏地址,一旦调用了readClass后我们会对其重新赋值,并且会为他设置名称,我们我对象的名称也就是在这里进行设置的。
  • 下面我们通过代码来调试一下

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

readClass

源码

  1. Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
  2. {
  3. // 获取类名
  4. const char *mangledName = cls->nonlazyMangledName();
  5. if (missingWeakSuperclass(cls)) {……}
  6. cls->fixupBackwardDeployingStableSwift();
  7. Class replacing = nil;
  8. if (mangledName != nullptr) {……}
  9. if (headerIsPreoptimized && !replacing) {……
  10. } else {
  11. if (mangledName) { //some Swift generic classes can lazily generate their names
  12. // 将类名和地址关联起来
  13. addNamedClass(cls, mangledName, replacing);
  14. } else {
  15. Class meta = cls->ISA();
  16. const class_ro_t *metaRO = meta->bits.safe_ro();
  17. ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
  18. ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
  19. }
  20. // 将关联好的类插入到另一张哈希表中 初始化完成的类列表
  21. addClassTableEntry(cls);
  22. }
  23. // for future reference: shared cache never contains MH_BUNDLEs
  24. if (headerIsBundle) {……}
  25. return cls;
  26. }

关键方法:

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

下面我们通过对cls进行过滤,研究我们自己创建的Person类
image.png
此过程通过nonlazyMangledName获取到了类的名称

nonlazyMangledName源码:

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

safe_ro源码:

  1. const class_ro_t *safe_ro() const {
  2. class_rw_t *maybe_rw = data();
  3. if (maybe_rw->flags & RW_REALIZED) {
  4. // maybe_rw is rw
  5. return maybe_rw->ro();
  6. } else {
  7. // maybe_rw is actually ro
  8. return (class_ro_t *)maybe_rw;
  9. }
  10. }

addNamedClass将类名和地址关联

  1. static void addNamedClass(Class cls, const char *name, Class replacing = nil)
  2. {
  3. runtimeLock.assertLocked();
  4. Class old;
  5. if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
  6. inform_duplicate(name, old, cls);
  7. // getMaybeUnrealizedNonMetaClass uses name lookups.
  8. // Classes not found by name lookup must be in the
  9. // secondary meta->nonmeta table.
  10. addNonMetaClass(cls);
  11. } else {
  12. // 更新存储所有类的哈希表 key是name value是cls
  13. NXMapInsert(gdb_objc_realized_classes, name, cls);
  14. }
  15. ASSERT(!(cls->data()->flags & RO_META));
  16. // wrong: constructed classes are already realized when they get here
  17. // ASSERT(!cls->isRealized());
  18. }

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

NXMapInsert源码:

  1. void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
  2. MapPair *pairs = (MapPair *)table->buckets;
  3. unsigned index = bucketOf(table, key);
  4. MapPair *pair = pairs + index;
  5. if (key == NX_MAPNOTAKEY) {
  6. _objc_inform("*** NXMapInsert: invalid key: -1\n");
  7. return NULL;
  8. }
  9. unsigned numBuckets = table->nbBucketsMinusOne + 1;
  10. if (pair->key == NX_MAPNOTAKEY) {
  11. pair->key = key; pair->value = value;
  12. table->count++;
  13. if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
  14. return NULL;
  15. }
  16. // …………省略
  17. }

addClassTableEntry源码

  1. static void
  2. addClassTableEntry(Class cls, bool addMeta = true)
  3. {
  4. runtimeLock.assertLocked();
  5. // This class is allowed to be a known class via the shared cache or via
  6. // data segments, but it is not allowed to be in the dynamic table already.
  7. // allocatedClasses就是_objc_alloc runtime_init中初始化的
  8. auto &set = objc::allocatedClasses.get();
  9. ASSERT(set.find(cls) == set.end());
  10. if (!isKnownClass(cls))
  11. set.insert(cls);
  12. if (addMeta)
  13. addClassTableEntry(cls->ISA(), false);
  14. }

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

通过对源码的分析和断电调试,我们发现rw和ro的获取和赋值并不是在readClass里面,那么重新回到_read_image继续向下看。
最终我们断电继续向下走,走到了类加载的区域发现,调用了realizeClassWithoutSwift方法,先透露一下,rw和ro的查找和赋值就是在这个方法中完成的,这个方法我们下节进行探讨
image.png