- 引言
上一篇我们探索到了dyld的加载流程。了解dyld与objc通信的_objc_init函数。我们了解了load_images赋值给dyld内部的sNotifyObjCInit,并回调执行所有+load方法。_objc_init中的_dyld_objc_notify_register函数仍有两个参数未讲到,本文将探究dyld与objc关联的_objc_init函数内部流程。
简而言之:dyld→libSystem→libdispatch→libobjc。交汇点就是_objc_init函数。该函数内部如下(已在对应函数添加了简单说明):">_objc_init_objc_init函数,是引导初始化程序,使用dyld注册我们的image镜像通知者,并由libSystem在库初始化调用。在_objc_init函数内打个断点,观察左边的调用栈,可知道_objc_init的调起过程。
简而言之:dyld→libSystem→libdispatch→libobjc。交汇点就是_objc_init函数。该函数内部如下(已在对应函数添加了简单说明):- ">总结:
dyld像objc暴露了API的函数:_dyld_objc_notify_register,这个函数接收来自objc方的实现,通过map_images,load_images,unmap_image,这三个参数来实现dyld与objc的通信。流程图如下所示。
引言
上一篇我们探索到了dyld的加载流程。了解dyld与objc通信的_objc_init函数。我们了解了load_images赋值给dyld内部的sNotifyObjCInit,并回调执行所有+load方法。_objc_init中的_dyld_objc_notify_register函数仍有两个参数未讲到,本文将探究dyld与objc关联的_objc_init函数内部流程。
_objc_init
_objc_init函数,是引导初始化程序,使用dyld注册我们的image镜像通知者,并由libSystem在库初始化调用。在_objc_init函数内打个断点,观察左边的调用栈,可知道_objc_init的调起过程。
简而言之:dyld→libSystem→libdispatch →libobjc。交汇点就是_objc_init函数。该函数内部如下(已在对应函数添加了简单说明):
/************************************************************************ _objc_init* Bootstrap initialization. Registers our image notifier with dyld.* Called by libSystem BEFORE library initialization time**********************************************************************/void _objc_init(void){static bool initialized = false;if (initialized) return;initialized = true;// fixme defer initialization until an objc-using image is found?1、读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助environ_init();2、关于线程key的绑定 - 比如每线程数据的析构函数tls_init();3、运行C ++静态构造函数。在dyld调用我们的静态构造函数之前,`libc` 会调用 _objc_init(), 因此我们必须自己做static_init();4、runtime运行时环境初始化,里面主要是:unattachedCategories,allocatedClassesruntime_init();5、初始化libobjc的异常处理系统exception_init();#if __OBJC2__6、缓存条件初始化cache_t::init();#endif7、启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib_imp_implementationWithBlock_init();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 方法时。_dyld_objc_notify_register(&map_images, load_images, unmap_image);#if __OBJC2__didCallDyldNotifyRegister = true;#endif}
【 environ_init 】
environ_init内部会读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助。可在environ_init内部看到如下代码:
environ_init内部的核心,就是这个for循环所能打印的东西。environ_init调试过程如下:
去掉if (PrintHelp || PrintOptions)条件,即可打印所有环境变量:
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);}
运行结果如下:
环境变量在Xcode对应的位置为:Edit Scheme → Run → Arguments → Environment

#####environ_init测试1:OBJC_PRINT_LOAD_METHODS:打印+load方法的位置。
按照上图方法,添加OBJC_PRINT_LOAD_METHODS,并勾选。在QLPerson.m中添加+load方法。运行程序。

#####environ_init测试2:OBJC_DISABLE_NONPOINTER_ISA:关闭nonpointer_isa
1)启用nonpointer_isa:main.m中创建person对象,打个断点并运行:
2)禁用nonpointer_isa:
运行结果:
3)所有环境变量表:
环境变量名 说明OBJC_PRINT_OPTIONS 输出OBJC已设置的选项OBJC_PRINT_IMAGES 输出已load的image信息OBJC_PRINT_LOAD_METHODS 打印 Class 及 Category 的 + (void)load 方法的调用信息OBJC_PRINT_INITIALIZE_METHODS 打印 Class 的 + (void)initialize 的调用信息OBJC_PRINT_RESOLVED_METHODS 打印通过 +resolveClassMethod: 或 +resolveInstanceMethod: 生成的类方法OBJC_PRINT_CLASS_SETUP 打印 Class 及 Category 的设置过程OBJC_PRINT_PROTOCOL_SETUP 打印 Protocol 的设置过程OBJC_PRINT_IVAR_SETUP 打印 Ivar 的设置过程OBJC_PRINT_VTABLE_SETUP 打印 vtable 的设置过程OBJC_PRINT_VTABLE_IMAGES 打印 vtable 被覆盖的方法OBJC_PRINT_CACHE_SETUP 打印方法缓存的设置过程OBJC_PRINT_FUTURE_CLASSES 打印从 CFType 无缝转换到 NSObject 将要使用的类(如 CFArrayRef 到 NSArray * )OBJC_PRINT_GC 打印一些垃圾回收操作OBJC_PRINT_PREOPTIMIZATION 打印 dyld 共享缓存优化前的问候语OBJC_PRINT_CXX_CTORS 打印类实例中的 C++ 对象的构造与析构调用OBJC_PRINT_EXCEPTIONS 打印异常处理OBJC_PRINT_EXCEPTION_THROW 打印所有异常抛出时的 BacktraceOBJC_PRINT_ALT_HANDLERS 打印 alt 操作异常处理OBJC_PRINT_REPLACED_METHODS 打印被 Category 替换的方法OBJC_PRINT_DEPRECATION_WARNINGS 打印所有过时的方法调用OBJC_PRINT_POOL_HIGHWATER 打印 autoreleasepool 高水位警告OBJC_PRINT_CUSTOM_RR 打印含有未优化的自定义 retain/release 方法的类OBJC_PRINT_CUSTOM_AWZ 打印含有未优化的自定义 allocWithZone 方法的类OBJC_PRINT_RAW_ISA 打印需要访问原始 isa 指针的类OBJC_DEBUG_UNLOAD 卸载有不良行为的 Bundle 时打印警告OBJC_DEBUG_FRAGILE_SUPERCLASSES 当子类可能被对父类的修改破坏时打印警告OBJC_DEBUG_FINALIZERS 警告实现了 -dealloc 却没有实现 -finalize 的类OBJC_DEBUG_NIL_SYNC 警告 @synchronized(nil) 调用,这种情况不会加锁OBJC_DEBUG_NONFRAGILE_IVARS 打印突发地重新布置 non-fragile ivars 的行为OBJC_DEBUG_ALT_HANDLERS 记录更多的 alt 操作错误信息OBJC_DEBUG_MISSING_POOLS 警告没有 pool 的情况下使用 autorelease,可能内存泄漏OBJC_DEBUG_DUPLICATE_CLASSES 当出现类重名时停机OBJC_USE_INTERNAL_ZONE 在一个专用的 malloc 区分配运行时数据OBJC_DISABLE_GC 强行关闭自动垃圾回收,即使可执行文件需要垃圾回收OBJC_DISABLE_VTABLES 关闭 vtable 分发OBJC_DISABLE_PREOPTIMIZATION 关闭 dyld 共享缓存优化前的问候语OBJC_DISABLE_TAGGED_POINTERS 关闭 NSNumber 等的 tagged pointer 优化OBJC_DISABLE_NONPOINTER_ISA 关闭 non-pointer isa 字段的访问来源:style_月月
【 tls_init 】
tls_init是关于线程key的绑定 - 比如每线程数据的析构函数
源码如下:
void tls_init(void){#if SUPPORT_DIRECT_THREAD_KEYSpthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);#else_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);#endif}
【 static_init 】
这里会运行 C++ 的静态构造函数,在 dyld 调用我们的静态构造函数之前,libc会调用 _objc_init,所以这里我们必须自己来处理,并且这里只会初始化系统内置的 C++ 静态构造函数,我们自己代码里面写的并不会在这里初始化。
换句话说:**static_init**主要是运行系统级别的**C++静态构造函数**,在**dyld**调用**我们自定义的静态构造函数**之前,**libc**调用**_objc_init**方法,即系统级别的**C++构造函数** 先于 **自定义的C++构造函数** 运行
/************************************************************************ 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(){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 】
运行时的初始化,主要分为两个操作:
1、开辟存储分类的表:objc::unattachedCategories.init(32);
2、开辟存储类的表:objc::allocatedClasses.init();
void runtime_init(void){objc::unattachedCategories.init(32);objc::allocatedClasses.init();}
【 exception_init 】
主要是初始化libobjc的异常处理系统。由map_image()函数调起。源码如下:
/************************************************************************ exception_init* Initialize libobjc's exception handling system.* Called by map_images().**********************************************************************/void exception_init(void){old_terminate = std::set_terminate(&_objc_terminate);}
1、old_terminate的定义是:static void (*old_terminate)(void) = nil;
2、_objc_terminate是一个函数名,其对应实现如下:
/************************************************************************ _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)((id)e);(*old_terminate)();} @catch (...) {// It's not an objc object. Continue to C++ terminate.(*old_terminate)();}}}
说明:
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这个赋值位置。则我们继续找出上层调用即可
2、我们平时抓取异常用到的NSSetUncaughtExceptionHandler,此处的void QLExceptionHandlers函数,赋值给fn,fn赋值给uncaught_handler。
+ (void)configExceptionHandler{// uncaught_handler() = fn = QLExceptionHandlers// objc_setUncaughtExceptionHandler()NSSetUncaughtExceptionHandler(&QLExceptionHandlers);}/// Exceptionvoid QLExceptionHandlers(NSException *exception) {NSLog(@"%s",__func__);}
【 cache_t::init() 】
cache_t::init()主要是:缓存条件初始化
【 _imp_implementationWithBlock_init】
启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
void _imp_implementationWithBlock_init(void){#if TARGET_OS_OSX在某些进程中急切地加载 libobjc-trampolines.dylib。一些程序(最显着的是旧版本的嵌入式 Chromium 使用的 QtWebEngineProcess)启用了一个高度限制的沙箱配置文件,该配置文件会阻止访问该 dylib。如果有任何东西调用了 imp_implementationWithBlock(就像 AppKit 开始做的那样),那么我们将在尝试加载它时崩溃。在启用沙箱配置文件并阻止它之前,在此处加载它会对其进行设置。if (__progname &&(strcmp(__progname, "QtWebEngineProcess") == 0 ||strcmp(__progname, "Steam Helper") == 0)) {Trampolines.Initialize();}#endif}
【 _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) 
####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内部递归监控dyld。
notifyMonitoringDyld源码如下(递归):
static void notifyMonitoringDyld(bool unloading, unsigned imageCount, const struct mach_header* loadAddresses[], const char* imagePaths[]){dyld3::ScopedTimer(DBG_DYLD_REMOTE_IMAGE_NOTIFIER, 0, 0, 0);RemoteNotificationResponder responder;if (!responder.active()) { return; }notifyMonitoringDyld(responder, unloading, imageCount, loadAddresses, imagePaths);}
2、map_images实现(objc):进入源码可见
处理由被dyld 映射的给定的image镜像。voidmap_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_nolock内部为执行所有类注册和修复(或延迟发现丢失的超类等),并调用 +load 方法。
####unmap_image
(当**dyld**把镜像文件删除时,会调用此函数)
处理即将被 dyld 取消映射的给定的镜像文件。与map_images和load_images流程类似,最终dyld在removeImage函数中,回调(*sNotifyObjCUnmapped)(image->getRealPath(), image->machHeader());其内部如下:
处理即将被 dyld 取消映射的给定的镜像文件。voidunmap_image(const char *path __unused, const struct mach_header *mh){recursive_mutex_locker_t lock(loadMethodLock);mutex_locker_t lock2(runtimeLock);unmap_image_nolock(mh);}
objc中实现删除镜像文件函数unmap_image_nolock:
