引言
上一篇我们探索到了dyld的加载流程。了解dyldobjc通信的_objc_init函数。我们了解了load_images赋值给dyld内部的sNotifyObjCInit,并回调执行所有+load方法。_objc_init中的_dyld_objc_notify_register函数仍有两个参数未讲到,本文将探究dyldobjc关联的_objc_init函数内部流程。

_objc_init
_objc_init函数,是引导初始化程序,使用dyld注册我们的image镜像通知者,并由libSystem在库初始化调用。在_objc_init函数内打个断点,观察左边的调用栈,可知道_objc_init的调起过程。011-iOS底层原理-_objc_init - 图3
简而言之:dyldlibSystemlibdispatchlibobjc。交汇点就是_objc_init函数。该函数内部如下(已在对应函数添加了简单说明):

  1. /***********************************************************************
  2. * _objc_init
  3. * Bootstrap initialization. Registers our image notifier with dyld.
  4. * Called by libSystem BEFORE library initialization time
  5. **********************************************************************/
  6. void _objc_init(void)
  7. {
  8. static bool initialized = false;
  9. if (initialized) return;
  10. initialized = true;
  11. // fixme defer initialization until an objc-using image is found?
  12. 1、读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助
  13. environ_init();
  14. 2、关于线程key的绑定 - 比如每线程数据的析构函数
  15. tls_init();
  16. 3、运行C ++静态构造函数。在dyld调用我们的静态构造函数之前,`libc` 会调用 _objc_init(), 因此我们必须自己做
  17. static_init();
  18. 4runtime运行时环境初始化,里面主要是:unattachedCategories,allocatedClasses
  19. runtime_init();
  20. 5、初始化libobjc的异常处理系统
  21. exception_init();
  22. #if __OBJC2__
  23. 6、缓存条件初始化
  24. cache_t::init();
  25. #endif
  26. 7、启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
  27. _imp_implementationWithBlock_init();
  28. 8、注册在映射、取消映射和初始化 objc 的镜像时要调用的处理程序。Dyld 将使用包含 objc-image-info 部分的images镜像数组回调“映射”函数。那些 dylib 的镜像将自动增加 ref-counts,因此 objc 将不再需要对它们调用 dlopen() 以防止它们被卸载。在调用 _dyld_objc_notify_register() 期间,dyld 将使用已加载的 objc image镜像调用“映射”函数。在以后的任何 dlopen() 调用中,dyld 还将调用“映射”函数。当 dyld 在该image镜像中被称为初始值设定项时,Dyld 将调用“init”函数。这是当 objc 调用该image镜像中的任何 +load 方法时。
  29. _dyld_objc_notify_register(&map_images, load_images, unmap_image);
  30. #if __OBJC2__
  31. didCallDyldNotifyRegister = true;
  32. #endif
  33. }

【 environ_init 】

environ_init内部会读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助。可在environ_init内部看到如下代码:011-iOS底层原理-_objc_init - 图4
environ_init内部的核心,就是这个for循环所能打印的东西。
environ_init调试过程如下:
去掉if (PrintHelp || PrintOptions)条件,即可打印所有环境变量:

  1. for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
  2. const option_t *opt = &Settings[i];
  3. _objc_inform("%s: %s", opt->env, opt->help);
  4. _objc_inform("%s is set", opt->env);
  5. }

运行结果如下:011-iOS底层原理-_objc_init - 图5
环境变量Xcode对应的位置为:Edit Scheme → Run → Arguments → Environment
011-iOS底层原理-_objc_init - 图6011-iOS底层原理-_objc_init - 图7
#####environ_init测试1:OBJC_PRINT_LOAD_METHODS:打印+load方法的位置。
按照上图方法,添加OBJC_PRINT_LOAD_METHODS,并勾选。在QLPerson.m中添加+load方法。运行程序。011-iOS底层原理-_objc_init - 图8011-iOS底层原理-_objc_init - 图9
#####environ_init测试2:OBJC_DISABLE_NONPOINTER_ISA:关闭nonpointer_isa
1)启用nonpointer_isamain.m中创建person对象,打个断点并运行:011-iOS底层原理-_objc_init - 图10
2)禁用nonpointer_isa011-iOS底层原理-_objc_init - 图11
运行结果:011-iOS底层原理-_objc_init - 图12
3)所有环境变量表:

  1. 环境变量名 说明
  2. OBJC_PRINT_OPTIONS 输出OBJC已设置的选项
  3. OBJC_PRINT_IMAGES 输出已loadimage信息
  4. OBJC_PRINT_LOAD_METHODS 打印 Class Category + (void)load 方法的调用信息
  5. OBJC_PRINT_INITIALIZE_METHODS 打印 Class + (void)initialize 的调用信息
  6. OBJC_PRINT_RESOLVED_METHODS 打印通过 +resolveClassMethod: +resolveInstanceMethod: 生成的类方法
  7. OBJC_PRINT_CLASS_SETUP 打印 Class Category 的设置过程
  8. OBJC_PRINT_PROTOCOL_SETUP 打印 Protocol 的设置过程
  9. OBJC_PRINT_IVAR_SETUP 打印 Ivar 的设置过程
  10. OBJC_PRINT_VTABLE_SETUP 打印 vtable 的设置过程
  11. OBJC_PRINT_VTABLE_IMAGES 打印 vtable 被覆盖的方法
  12. OBJC_PRINT_CACHE_SETUP 打印方法缓存的设置过程
  13. OBJC_PRINT_FUTURE_CLASSES 打印从 CFType 无缝转换到 NSObject 将要使用的类(如 CFArrayRef NSArray *
  14. OBJC_PRINT_GC 打印一些垃圾回收操作
  15. OBJC_PRINT_PREOPTIMIZATION 打印 dyld 共享缓存优化前的问候语
  16. OBJC_PRINT_CXX_CTORS 打印类实例中的 C++ 对象的构造与析构调用
  17. OBJC_PRINT_EXCEPTIONS 打印异常处理
  18. OBJC_PRINT_EXCEPTION_THROW 打印所有异常抛出时的 Backtrace
  19. OBJC_PRINT_ALT_HANDLERS 打印 alt 操作异常处理
  20. OBJC_PRINT_REPLACED_METHODS 打印被 Category 替换的方法
  21. OBJC_PRINT_DEPRECATION_WARNINGS 打印所有过时的方法调用
  22. OBJC_PRINT_POOL_HIGHWATER 打印 autoreleasepool 高水位警告
  23. OBJC_PRINT_CUSTOM_RR 打印含有未优化的自定义 retain/release 方法的类
  24. OBJC_PRINT_CUSTOM_AWZ 打印含有未优化的自定义 allocWithZone 方法的类
  25. OBJC_PRINT_RAW_ISA 打印需要访问原始 isa 指针的类
  26. OBJC_DEBUG_UNLOAD 卸载有不良行为的 Bundle 时打印警告
  27. OBJC_DEBUG_FRAGILE_SUPERCLASSES 当子类可能被对父类的修改破坏时打印警告
  28. OBJC_DEBUG_FINALIZERS 警告实现了 -dealloc 却没有实现 -finalize 的类
  29. OBJC_DEBUG_NIL_SYNC 警告 @synchronized(nil) 调用,这种情况不会加锁
  30. OBJC_DEBUG_NONFRAGILE_IVARS 打印突发地重新布置 non-fragile ivars 的行为
  31. OBJC_DEBUG_ALT_HANDLERS 记录更多的 alt 操作错误信息
  32. OBJC_DEBUG_MISSING_POOLS 警告没有 pool 的情况下使用 autorelease,可能内存泄漏
  33. OBJC_DEBUG_DUPLICATE_CLASSES 当出现类重名时停机
  34. OBJC_USE_INTERNAL_ZONE 在一个专用的 malloc 区分配运行时数据
  35. OBJC_DISABLE_GC 强行关闭自动垃圾回收,即使可执行文件需要垃圾回收
  36. OBJC_DISABLE_VTABLES 关闭 vtable 分发
  37. OBJC_DISABLE_PREOPTIMIZATION 关闭 dyld 共享缓存优化前的问候语
  38. OBJC_DISABLE_TAGGED_POINTERS 关闭 NSNumber 等的 tagged pointer 优化
  39. OBJC_DISABLE_NONPOINTER_ISA 关闭 non-pointer isa 字段的访问
  40. 来源:style_月月

【 tls_init 】

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++ 的静态构造函数,在 dyld 调用我们的静态构造函数之前,libc会调用 _objc_init,所以这里我们必须自己来处理,并且这里只会初始化系统内置的 C++ 静态构造函数,我们自己代码里面写的并不会在这里初始化。
换句话说:
**static_init**主要是运行系统级别的**C++静态构造函数**,在**dyld**调用**我们自定义的静态构造函数**之前,**libc**调用**_objc_init**方法,即系统级别的**C++构造函数** 先于 **自定义的C++构造函数** 运行

  1. /***********************************************************************
  2. * static_init
  3. * Run C++ static constructor functions.
  4. * libc calls _objc_init() before dyld would call our static constructors,
  5. * so we have to do it ourselves.
  6. **********************************************************************/
  7. static void static_init()
  8. {
  9. size_t count;
  10. auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
  11. for (size_t i = 0; i < count; i++) {
  12. inits[i]();
  13. }
  14. auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
  15. for (size_t i = 0; i < count; i++) {
  16. UnsignedInitializer init(offsets[i]);
  17. init();
  18. }
  19. }

【 runtime_init 】

运行时的初始化,主要分为两个操作:
1、开辟存储分类的表:objc::unattachedCategories.init(32);
2、开辟存储类的表:objc::allocatedClasses.init();

  1. void runtime_init(void)
  2. {
  3. objc::unattachedCategories.init(32);
  4. objc::allocatedClasses.init();
  5. }

【 exception_init 】

主要是初始化libobjc的异常处理系统。由map_image()函数调起。源码如下:

  1. /***********************************************************************
  2. * exception_init
  3. * Initialize libobjc's exception handling system.
  4. * Called by map_images().
  5. **********************************************************************/
  6. void exception_init(void)
  7. {
  8. old_terminate = std::set_terminate(&_objc_terminate);
  9. }

1、old_terminate的定义是:static void (*old_terminate)(void) = nil;
2、_objc_terminate是一个函数名,其对应实现如下:

  1. /***********************************************************************
  2. * _objc_terminate
  3. * Custom std::terminate handler.
  4. *
  5. * The uncaught exception callback is implemented as a std::terminate handler.
  6. * 1. Check if there's an active exception
  7. * 2. If so, check if it's an Objective-C exception
  8. * 3. If so, call our registered callback with the object.
  9. * 4. Finally, call the previous terminate handler.
  10. **********************************************************************/
  11. static void (*old_terminate)(void) = nil;
  12. static void _objc_terminate(void)
  13. {
  14. if (PrintExceptions) {
  15. _objc_inform("EXCEPTIONS: terminating");
  16. }
  17. if (! __cxa_current_exception_type()) {
  18. // No current exception.
  19. (*old_terminate)();
  20. }
  21. else {
  22. // There is a current exception. Check if it's an objc exception.
  23. @try {
  24. __cxa_rethrow();
  25. } @catch (id e) {
  26. // It's an objc object. Call Foundation's handler, if any.
  27. (*uncaught_handler)((id)e);
  28. (*old_terminate)();
  29. } @catch (...) {
  30. // It's not an objc object. Continue to C++ terminate.
  31. (*old_terminate)();
  32. }
  33. }
  34. }

说明:
a、未捕获的异常回调作为 std::terminate 处理程序实现。
b、检查是否有处于活动状态的异常
c、如果是,请检查它是否是 Objective-C 异常
d、如果是这样,回调到我们已经注册好的回调函数。
e、最后,调用前一个终止处理程序。

当异常发生时,回来到static void _objc_terminate(void)函数,最终由(*uncaught_handler)((id)e);抛出异常。uncaught_handler的探索与dyld中的sNotifyObjCInit类似。由他人传进来
1、全局搜索uncaught_handler ,找到uncaught_handler = fn这个赋值位置。则我们继续找出上层调用即可011-iOS底层原理-_objc_init - 图13
2、我们平时抓取异常用到的NSSetUncaughtExceptionHandler,此处的void QLExceptionHandlers函数,赋值给fnfn赋值给uncaught_handler

  1. + (void)configExceptionHandler{
  2. // uncaught_handler() = fn = QLExceptionHandlers
  3. // objc_setUncaughtExceptionHandler()
  4. NSSetUncaughtExceptionHandler(&QLExceptionHandlers);
  5. }
  6. /// Exception
  7. void QLExceptionHandlers(NSException *exception) {
  8. NSLog(@"%s",__func__);
  9. }

【 cache_t::init() 】

cache_t::init()主要是:缓存条件初始化

【 _imp_implementationWithBlock_init】

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

  1. void _imp_implementationWithBlock_init(void)
  2. {
  3. #if TARGET_OS_OSX
  4. 在某些进程中急切地加载 libobjc-trampolines.dylib
  5. 一些程序(最显着的是旧版本的嵌入式 Chromium 使用的 QtWebEngineProcess)启用了一个高度限制的沙箱配置文件,该配置文件会阻止访问该 dylib
  6. 如果有任何东西调用了 imp_implementationWithBlock(就像 AppKit 开始做的那样),那么我们将在尝试加载它时崩溃。在启用沙箱配置文件并阻止它之前,在此处加载它会对其进行设置。
  7. if (__progname &&
  8. (strcmp(__progname, "QtWebEngineProcess") == 0 ||
  9. strcmp(__progname, "Steam Helper") == 0)) {
  10. Trampolines.Initialize();
  11. }
  12. #endif
  13. }

【 _dyld_objc_notify_register】

注册在映射、取消映射和初始化 dyld 的镜像时要调用的处理程序。dyld 将使用包含 objc-image-info 部分的images镜像数组回调“映射”函数。那些 dylib 的镜像将自动增加 ref-counts,因此 objc 将不再需要对它们调用 dlopen() 以防止它们被卸载。在调用 _dyld_objc_notify_register() 期间,dyld 将使用已加载的 objc image镜像调用“映射”函数。在以后的任何 dlopen()调用中,dyld 还将调用“映射”函数。当 dyld 在该image镜像中被称为初始值设定项时,dyld 将调用init函数。这是当 objc 调用该image镜像中的任何 +load 方法时。
####load_images
(当**dyld**把镜像文件初始化时,会调用此函数)
上篇文章,我们探索到dyld通过_dyld_objc_notify_register注册load_images函数,赋值给init,然后在递归初始化中调用notifySingle时回调(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());执行load_images函数,加载所有+load方法。(sNotifyObjCInit = init = load_images) 011-iOS底层原理-_objc_init - 图14
####map_images
(当**dyld**把镜像文件加载入内存时,会调用此函数)
_dyld_objc_notify_register的第一个参数&map_images传的是map_images函数的指针,原因如下:
因为这个map_images很重要,必须要保证dyld内部加载镜像与map_image的实现保持同步发生变化,内容一致map_images是映射所有镜像文件,是一个比较耗时的过程,如果两者之间没有发生同步变化,会发生错乱现象,导致整个程序发生错乱。而load_images的流程只是为了回调objc加载+load方法,无需传函数指针。

1、流程(dyld):与load_images一样,dyld通过_dyld_objc_notify_register注册&map_images指针,赋值给sNotifyObjCMapped,并立即调用notifyBatchPartial函数,执行(*sNotifyObjCMapped)(objcImageCount, paths, mhs);回调。同时调用notifyMonitoringDyld,并在notifyMonitoringDyld内部递归监控dyld011-iOS底层原理-_objc_init - 图15
notifyMonitoringDyld源码如下(递归):

  1. static void notifyMonitoringDyld(bool unloading, unsigned imageCount, const struct mach_header* loadAddresses[], const char* imagePaths[])
  2. {
  3. dyld3::ScopedTimer(DBG_DYLD_REMOTE_IMAGE_NOTIFIER, 0, 0, 0);
  4. RemoteNotificationResponder responder;
  5. if (!responder.active()) { return; }
  6. notifyMonitoringDyld(responder, unloading, imageCount, loadAddresses, imagePaths);
  7. }

2、map_images实现(objc):进入源码可见

  1. 处理由被dyld 映射的给定的image镜像。
  2. void
  3. map_images(unsigned count, const char * const paths[],
  4. const struct mach_header * const mhdrs[])
  5. {
  6. mutex_locker_t lock(runtimeLock);
  7. return map_images_nolock(count, paths, mhdrs);
  8. }

011-iOS底层原理-_objc_init - 图16

map_images_nolock内部为执行所有类注册和修复(或延迟发现丢失的超类等),并调用 +load 方法。
####unmap_image
(当**dyld**把镜像文件删除时,会调用此函数)
处理即将被 dyld 取消映射的给定的镜像文件。与map_images和load_images流程类似,最终dyldremoveImage函数中,回调(*sNotifyObjCUnmapped)(image->getRealPath(), image->machHeader());其内部如下:

  1. 处理即将被 dyld 取消映射的给定的镜像文件。
  2. void
  3. unmap_image(const char *path __unused, const struct mach_header *mh)
  4. {
  5. recursive_mutex_locker_t lock(loadMethodLock);
  6. mutex_locker_t lock2(runtimeLock);
  7. unmap_image_nolock(mh);
  8. }

objc中实现删除镜像文件函数unmap_image_nolock
011-iOS底层原理-_objc_init - 图17

总结:
dyldobjc暴露了API的函数:_dyld_objc_notify_register,这个函数接收来自objc方的实现,通过map_imagesload_imagesunmap_image,这三个参数来实现dyldobjc的通信。流程图如下所示。
011-iOS底层原理-_objc_init - 图18