iOS-底层探索12(dyld和objc的关联分析(类的加载上))

iOS 底层探索 文章汇总
目录
  • 一、前言
  • 二、_objc_init方法分析
  • 三、_dyld_objc_notify_register方法分析
  • 四、map_images 方法分析
  • 五、_read_images 方法分析
  • 六、readClass 方法分析


一、前言 上一篇文章iOS dyld流程分析中我们分析了dyld流程,知道了dyld会将库和代码编译加载到内存中。然后通过读取macho文件中的data获取到类信息,其中data中包含了ro、rw、rwe。后面的学习中我们将探索方法、属性、协议什么时候添加到类中的,rwe什么时候产生的?那么这篇文章我们就先分析dyldobjc的关联关系。
二、_objc_init方法分析 dyld加载库和代码后会进行objc的初始化,调用的方法为:_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_init(); static_init(); runtime_init(); exception_init(); cache_init(); _imp_implementationWithBlock_init(); // 什么时候调用? images 镜像文件 // map_images() // load_images()_dyld_objc_notify_register(&map_images, load_images, unmap_image); #if __OBJC2__ didCallDyldNotifyRegister = true; #endif }

environ_init()方法为运行时环境的初始化,打印出所有运行时环境变量
iOS-底层探索12(dyld和objc的关联分析(类的加载上))
文章图片
environ_init 运行代码打印结果如下:
iOS-底层探索12(dyld和objc的关联分析(类的加载上))
文章图片
image.png 由此可知设置环境变量OBJC_PRINT_LOAD_METHODS即可打印+load 方法
iOS-底层探索12(dyld和objc的关联分析(类的加载上))
文章图片
image.png 所有调用了+load方法的类均会打印出来:
iOS-底层探索12(dyld和objc的关联分析(类的加载上))
文章图片
image.png _objc_init中调用的各个方法说明
  • environ_init():读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助。
  • tls_init():关于线程key的绑定-比如每条线程数据的析构函数。
  • static_init():运行C++静态构造函数。在dyld调用我们的静态构造函数之前,libc会调用_objc_init(),因此我们必须自己做。
  • runtime_init()runtime运行时环境初始化,里面主要是:unattachedCategoriesallocatedClasses后面会分析
  • exception_init():初始化libobjc的异常处理系统
  • cache_init():缓存条件初始化
  • _imp_implementationWithBlock_init():启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
  • _dyld_objc_notify_register(&map_images, load_images, unmap_image)dyld注册的地方。
其中的重点就是map_images方法和load_images方法的调用


三、_dyld_objc_notify_register方法分析 后续加载流程为:
iOS-底层探索12(dyld和objc的关联分析(类的加载上))
文章图片
image.png 然而_dyld_objc_notify_register的调用在objc源码中,_dyld_objc_notify_register的实现却在dyld源码中,因此这里存在跨库调用。
_dyld_objc_notify_register实现如下:
void _dyld_objc_notify_register(_dyld_objc_notify_mappedmapped, _dyld_objc_notify_initinit, _dyld_objc_notify_unmappedunmapped) { dyld::registerObjCNotifiers(mapped, init, unmapped); }

load_images赋值给sNotifyObjCInit
iOS-底层探索12(dyld和objc的关联分析(类的加载上))
文章图片
image.png 这里找到load_images方法的调用

iOS-底层探索12(dyld和objc的关联分析(类的加载上))
文章图片
image.png
同理也找到map_images方法的调用
static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification) { ... (*sNotifyObjCMapped)(objcImageCount, paths, mhs); ... }

map_images方法先调用,load_images方法后调用


四、map_images 方法分析 map_images方法的实现在objc源码中
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); }

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_image
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { ... if (!doneOnce) {...}// Fix up @selector references // 简单的字符串 -- 地址 字符串 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"); #endifbool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots(); // Discover protocols. Fix up protocol refs. 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) {...}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"); if (DebugNonFragileIvars) { realizeAllClasses(); }// Print preoptimization statistics if (PrintPreopt) {...} }

_read_images方法功能如下:
  1. 条件控制进行一次的加载
  2. 修复预编译阶段的@selector混乱问题
  3. 错误混乱的类处理
  4. 修复重映射一些没有被镜像文件加载进来的类
  5. 修复一些消息!
  6. 当我们类里面有协议的时候:readProtocol
  7. 修复没有被加载的协议
  8. 分类处理
  9. 类的加载处理:实现非懒加载类(实现了+load方法的类和静态实例)
  10. 没有被处理的类 优化哪些被侵犯的类
【iOS-底层探索12(dyld和objc的关联分析(类的加载上))】

五、_read_images 方法分析 在_read_images方法中我们发现这样的代码:
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { ... 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; } } ...

readClass方法的作用:
iOS-底层探索12(dyld和objc的关联分析(类的加载上))
文章图片
iOS-底层探索12(dyld和objc的关联分析(类的加载上))
文章图片
通过lldb断点调试我们发现从macho文件读取的cls经过readClass方法后cls绑定了的信息。


六、readClass 方法分析
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { const char *mangledName = cls->mangledName(); if (missingWeakSuperclass(cls)) {... }cls->fixupBackwardDeployingStableSwift(); Class replacing = nil; if (Class newCls = popFutureNamedClass(mangledName)) {...}if (headerIsPreoptimized&&!replacing) { // class list built in shared cache // fixme strict assert doesn't work because of duplicates // ASSERT(cls == getClass(name)); ASSERT(getClassExceptSomeSwift(mangledName)); } else { addNamedClass(cls, mangledName, replacing); 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方法中如下代码就将类的信息从macho中读取到了内存中,并加入类的实体表中。但此时内存中仅有类的地址和名字,还没有ro、rw、rew等数据。
addNamedClass(cls, mangledName, replacing); addClassTableEntry(cls);

    推荐阅读