objc的mapImage与loadImage底层探索

0a000581fca74098bc53b876f5e957ae.jpeg
上一章应用加载流程探索我们已经了解了,程序启动加载的流程。编译过程:预编译->编译->汇编->链接->可执行mach-o。然后链接过程用到了dyld动态链接器。主要靠dyld来链接管理,了解了dyld的加载动态库流程。\
9大步骤:\
1.环境变量配置(环境,平台,版本,路径,主机信息)\
2.共享缓存(mapSharedCache):(UIKit、CoreFoundation等)\
3.主程序的初始化(instantiateFromLoadedImage)\
4.加入动态库(loadInsertedDylib)\
5.link主程序\
6.link动态库\
7.weakBind弱引用绑定主程序\
8.initializeMainExecutable初始化\
9.notifyMonitoringDyldMain通知dyld可以进行main()

深入分析了initializeMainExecutable 初始化流程,引入了今天研究的核心mapImage和loadImage。
在程序动态连接器初始化动态库中mapImage和loadImage的作用。

一. _objc_init初始化流程

1、环境变量的初始化

废话不多说,直接上代码:

  1. void environ_init(void)
  2. {
  3. ....省略
  4. if (0 != strncmp(*p, "OBJC_", 5)) continue;
  5. if (0 == strncmp(*p, "OBJC_HELP=", 10)) {
  6. PrintHelp = true;
  7. continue;
  8. }
  9. if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {
  10. PrintOptions = true;
  11. continue;
  12. }
  13. if (0 == strncmp(*p, "OBJC_DEBUG_POOL_DEPTH=", 22)) {
  14. SetPageCountWarning(*p + 22);
  15. continue;
  16. }
  17. ....省略
  18. }

代码对应xcode的设置
image.png
只保留:
image.png
运行代码看控制台打印:
image.png
我们就可以通过OBJC_DISABLE_NONPOINTER_ISA来设置xcode对isa的指针优化开关。
image.png
验证打印:
image.png
isa指针优化关闭,然后我们设置OBJC_PRINT_IMAGES=YES我们打印看看:
image.png
打印了加载的动态库信息。

2、_objc_init解析

在objc4-838.1中添加代码:
image.png

  1. @interface NYPerson : NSObject
  2. @end
  3. @implementation NYPerson
  4. __attribute__((constructor)) static void function1(){
  5. printf("function1 \n");
  6. }
  7. +(void)load
  8. {
  9. NSLog(@"%s",__func__);
  10. }
  11. @end
  12. //打印结果
  13. **function**
  14. **2022-05-22 12:24:43.678216+0800 SXObjcDebug[13073:387196] +[NYPerson load]**
  15. **function1**
  16. **2022-05-22 12:24:43.680619+0800 SXObjcDebug[13073:387196] Hello World!**
  17. **Program ended with exit code: 0**

从打印结果得知,oc的static_init()中C++析构函数function在load之前执行。我们自己定义的C++析构函数function1在load之后执行。
image.png
由上图可知,_objc_init初始化配置了环境变量,创建线程的析构函数,初始化static静态数据,
分类表初始化,类表初始化,异常处理,缓存等操作。后面我们重点研究map_imagesload_images具体做了哪些处理。

3、load_images解析

来来来我们直接看代码:

  1. void
  2. load_images(const char *path __unused, const struct mach_header *mh)
  3. {
  4. if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
  5. didInitialAttachCategories = true;
  6. loadAllCategories();
  7. }
  8. // Return without taking locks if there are no +load methods here.
  9. if (!hasLoadMethods((const headerType *)mh)) return;
  10. recursive_mutex_locker_t lock(loadMethodLock);//加锁
  11. // Discover load methods
  12. // 查找 load 方法
  13. {
  14. mutex_locker_t lock2(runtimeLock);
  15. prepare_load_methods((const headerType *)mh);
  16. }
  17. // Call +load methods (without runtimeLock - re-entrant)
  18. //调用 +load 方法 ,包括类,和分类的load
  19. call_load_methods();
  20. }

我们进入prepare_load_methods看看具体实现:

  1. void prepare_load_methods(const headerType *mhdr)
  2. {
  3. size_t count, i;
  4. runtimeLock.assertLocked();
  5. //非懒加载类,重写了load方法的类
  6. classref_t const *classlist =
  7. _getObjc2NonlazyClassList(mhdr, &count);
  8. for (i = 0; i < count; i++) {
  9. schedule_class_load(remapClass(classlist[i]));//递归查找
  10. }
  11. //非懒加载的分类
  12. category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
  13. for (i = 0; i < count; i++) {
  14. category_t *cat = categorylist[i];
  15. Class cls = remapClass(cat->cls);
  16. if (!cls) continue; // category for ignored weak-linked class
  17. if (cls->isSwiftStable()) {
  18. _objc_fatal("Swift class extensions and categories on Swift "
  19. "classes are not allowed to have +load methods");
  20. }
  21. realizeClassWithoutSwift(cls, nil);
  22. ASSERT(cls->ISA()->isRealized());
  23. add_category_to_loadable_list(cat);//添加到分类表中
  24. }
  25. }
  26. //进入add_category_to_loadable_list方法
  27. void add_category_to_loadable_list(Category cat)
  28. {
  29. IMP method;
  30. .........省略............
  31. loadable_categories[loadable_categories_used].cat = cat;
  32. loadable_categories[loadable_categories_used].method = method;
  33. loadable_categories_used++;
  34. }

在进入schedule_class_load看代码:

  1. static void schedule_class_load(Class cls)
  2. {
  3. //不需要调用 super load --父类的load 优先添加到表中 --子类
  4. if (!cls) return;
  5. ASSERT(cls->isRealized()); // _read_images should realize
  6. if (cls->data()->flags & RW_LOADED) return;
  7. // Ensure superclass-first ordering
  8. schedule_class_load(cls->getSuperclass());//在父类中找load
  9. add_class_to_loadable_list(cls);
  10. cls->setInfo(RW_LOADED);
  11. }
  12. //在进入add_class_to_loadable_list方法
  13. void add_class_to_loadable_list(Class cls)
  14. {
  15. IMP method;
  16. loadMethodLock.assertLocked();
  17. .........省略............
  18. //添加cls,method到表中
  19. loadable_classes[loadable_classes_used].cls = cls;
  20. loadable_classes[loadable_classes_used].method = method;
  21. loadable_classes_used++;
  22. }

小结:load_images解析

1.当⽗类和⼦类都实现load函数时,⽗类的load⽅法执⾏顺序要优先于⼦类

2.当⼀个类未实现load⽅法时,不会调⽤⽗类load⽅法

3.类中的load⽅法执⾏顺序要优先于分类(Category)

4.load⽅法使⽤了锁,所以是线程安全的。

5.当有多个类别(Category)都实现了load⽅法,这⼏个load⽅法都会执⾏,但执⾏顺序不确定(其执⾏
顺序与类别在Compile Sources中出现的顺序⼀致)

6.当然当有多个不同的类的时候,每个类load 执⾏顺序与其在Compile Sources出现的顺序⼀致

4、read_images解析

我们在objc4-838.1中找到对应代码map_images
image.png
进入map_images_nolock代码:

  1. void
  2. map_images_nolock(unsigned mhCount, const char * const mhPaths[],
  3. const struct mach_header * const mhdrs[])
  4. {
  5. static bool firstTime = YES;
  6. header_info *hList[mhCount];
  7. uint32_t hCount;
  8. size_t selrefCount = 0;
  9. // Perform first-time initialization if necessary.
  10. // This function is called before ordinary library initializers.
  11. // fixme defer initialization until an objc-using image is found?
  12. if (firstTime) {
  13. preopt_init();//共享缓存的优化处理
  14. }
  15. if (PrintImages) {
  16. _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
  17. }
  18. .........省略............
  19. // 统计所有的类
  20. int totalClasses = 0;
  21. int unoptimizedTotalClasses = 0;
  22. {
  23. .........省略............
  24. }
  25. if (firstTime) {
  26. sel_init(selrefCount);//c++的构造函数析构方法初始化
  27. arr_init();//初始化自动释放池
  28. .........省略............
  29. }
  30. .........省略............
  31. if (hCount > 0) {
  32. //研究的重点
  33. _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
  34. }
  35. firstTime = NO;
  36. // Call image load funcs after everything is set up.
  37. for (auto func : loadImageFuncs) {
  38. for (uint32_t i = 0; i < mhCount; i++) {
  39. func(mhdrs[i]);
  40. }
  41. }
  42. }

sel_init的主要作用:c++的构造函数析构方法初始化
image.png
arr_init的主要作用:初始化自动释放池,散列表,关联对象初始化。
image.png
我们进入_read_images代码中:

  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. .........省略............
  14. for (EACH_HEADER) {
  15. if (hi->info()->containsSwift() &&
  16. hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
  17. {
  18. DisableNonpointerIsa = true;//指针优化
  19. if (PrintRawIsa) {
  20. _objc_inform("RAW ISA: disabling non-pointer isa because "
  21. "the app or a framework contains Swift code "
  22. "older than Swift 3.0");
  23. }
  24. break;
  25. }
  26. }
  27. .........省略............
  28. int namedClassesSize =
  29. (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
  30. gdb_objc_realized_classes =
  31. NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
  32. ts.log("IMAGE TIMES: first time tasks");
  33. .........省略............
  34. static size_t UnfixedSelectors;
  35. {
  36. mutex_locker_t lock(selLock);
  37. for (EACH_HEADER) {
  38. if (hi->hasPreoptimizedSelectors()) continue;
  39. bool isBundle = hi->isBundle();
  40. SEL *sels = _getObjc2SelectorRefs(hi, &count);
  41. UnfixedSelectors += count;
  42. for (i = 0; i < count; i++) {
  43. const char *name = sel_cname(sels[i]);
  44. SEL sel = sel_registerNameNoLock(name, isBundle);
  45. if (sels[i] != sel) {
  46. sels[i] = sel;//rebase //修护内存指针
  47. }
  48. }
  49. }
  50. }
  51. //ASLR 虚拟内存 的binding过程
  52. ts.log("IMAGE TIMES: fix up selector references");
  53. .........省略............
  54. #if SUPPORT_FIXUP
  55. // Fix up old objc_msgSend_fixup call sites
  56. for (EACH_HEADER) {
  57. message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
  58. if (count == 0) continue;
  59. if (PrintVtables) {
  60. _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
  61. "call sites in %s", count, hi->fname());
  62. }
  63. for (i = 0; i < count; i++) {
  64. fixupMessageRef(refs+i);
  65. }
  66. }
  67. ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
  68. #endif
  69. //将所有`Protocol`都添加到`protocol_map`表中
  70. for (EACH_HEADER) {
  71. extern objc_class OBJC_CLASS_$_Protocol;
  72. Class cls = (Class)&OBJC_CLASS_$_Protocol;
  73. ASSERT(cls);
  74. NXMapTable *protocol_map = protocols();
  75. bool isPreoptimized = hi->hasPreoptimizedProtocols();
  76. .........省略............
  77. }
  78. .........省略............
  79. //初始化所有⾮懒加载的类,进⾏`rw、ro`等操作
  80. for (EACH_HEADER) {
  81. classref_t const *classlist = hi->nlclslist(&count);
  82. for (i = 0; i < count; i++) {
  83. Class cls = remapClass(classlist[i]);
  84. if (!cls) continue;
  85. addClassTableEntry(cls);//递归调用加入表中
  86. if (cls->isSwiftStable()) {
  87. if (cls->swiftMetadataInitializer()) {
  88. _objc_fatal("Swift class %s with a metadata initializer "
  89. "is not allowed to be non-lazy",
  90. cls->nameForLogging());
  91. }
  92. }
  93. realizeClassWithoutSwift(cls, nil);
  94. }
  95. }
  96. .........省略............
  97. }

进入NXCreateMapTable看看做了什么?
image.png
创建了gdb_objc_realized_classes 这张表。\
主要看readClass:方法的作用:
image.png
我们在readClass中添加调试代码:

  1. Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
  2. {
  3. const char *mangledName = cls->nonlazyMangledName();
  4. const char *personName = "NYPerson";
  5. auto ny_ro = (const class_ro_t *)cls->data();
  6. auto ny_isMeta = ny_ro->flags & RO_META;
  7. if(strcasecmp(mangledName, personName)==0 && !ny_isMeta) //过滤元类
  8. {
  9. printf("NYPerson");
  10. }
  11. .........省略............
  12. }

进行断点调试,研究readClass都做了什么?
image.png
通过断点我们发现并没有进行 or,rw等操作。
image.png
添加类到表中,并返回这个class.

小结

1: 加载所有类到类的gdb_objc_realized_classes表中。

2: 对所有类做重映射。

3: 将所有SEL都注册到namedSelectors表中。

4: 修复函数指针遗留。

5: 将所有Protocol都添加到protocol_map表中。

6: 对所有Protocol做重映射。

7: 初始化所有⾮懒加载的类,进⾏rw、ro等操作。

8:遍历已标记的懒加载的类,并做初始化操作。

9:处理所有Category,包括ClassMeta Class

10:初始化所有未初始化的类

5、非懒加载类的加载

用上面同样的方法进行断点调试:

  1. static Class realizeClassWithoutSwift(Class cls, Class previously)
  2. {
  3. runtimeLock.assertLocked();
  4. .........省略............
  5. auto ro = (const class_ro_t *)cls->data();
  6. auto isMeta = ro->flags & RO_META;
  7. const char *mangledName = cls->nonlazyMangledName();
  8. const char *personName = "NYPerson";
  9. auto ny_ro = (const class_ro_t *)cls->data();
  10. auto ny_isMeta = ny_ro->flags & RO_META;
  11. if(strcasecmp(mangledName, personName)==0 && !ny_isMeta)
  12. {
  13. printf("NYPerson");
  14. }
  15. .........省略............
  16. //给rw开辟内存空间,然后将ro的数据“拷⻉”到rw⾥⾯。
  17. rw = objc::zalloc<class_rw_t>();
  18. rw->set_ro(ro);
  19. rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
  20. cls->setData(rw);
  21. .........省略............
  22. //递归调⽤realizeClassWithoutSwift,对⽗类和元类进⾏初始化
  23. supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
  24. metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
  25. .........省略............
  26. //设置⽗类,isa指针的初始化
  27. cls->setSuperclass(supercls);
  28. cls->initClassIsa(metacls);
  29. }

添加NYPerson代码:

  1. @interface NYPerson : NSObject
  2. @property (nonatomic,copy) NSString *name;
  3. @property (nonatomic,copy) NSString *gogo;
  4. - (void)test1;
  5. - (void)test2;
  6. - (void)test3;
  7. @end
  8. @implementation NYPerson
  9. __attribute__((constructor)) static void function1(){
  10. printf("function1 \n");
  11. }
  12. +(void)load
  13. {
  14. NSLog(@"%s",__func__);
  15. }
  16. - (void)test1{}
  17. - (void)test2{}
  18. - (void)test3{}
  19. @end

打印结果:
image.png
image.png
打印中发现realizeClassWithoutSwift中对类进行了or rw操作。

小结
realizeClassWithoutSwift对非懒加载类进行加载,并给rw开辟内存空间,然后将ro的数据“拷⻉”到rw⾥⾯。递归调⽤realizeClassWithoutSwift,对⽗类和元类进⾏初始化。最后设置⽗类,isa指针的初始化。

6、总结

1.环境变量的初始化:通过xcode设置或代码设置OBJC_DEBUG_POOL_DEPTH等设置,可以开启对应log打印。

2._objc_init解析:

  • environ_init():环境变量的初始化
  • tls_init:创建线程的析构函数
  • static_init:运⾏C++静态构造函数
  • runtime_init:分类表初始化,类表初始化
  • cache_t::init():缓存的初始化
  • _imp_implementationWithBlock_init:关于macOS的相关操作。
  • didCallDyldNotifyRegister:标识对_dyld_objc_notify_register的调⽤已完成。

3.load_images解析:

  • 1.当⽗类和⼦类都实现load函数时,⽗类的load⽅法执⾏顺序要优先于⼦类
  • 当⼀个类未实现load⽅法时,不会调⽤⽗类load⽅法
  • 类中的load⽅法执⾏顺序要优先于分类(Category)
  • load⽅法使⽤了锁,所以是线程安全的。
  • 当有多个类别(Category)都实现了load⽅法,这⼏个load⽅法都会执⾏,但执⾏顺序不确定(其执⾏顺序与类别在Compile Sources中出现的顺序⼀致)
  • 当然当有多个不同的类的时候,每个类load 执⾏顺序与其在Compile Sources出现的顺序⼀致
    4.read_images解析:
  • 加载所有类到类的gdb_objc_realized_classes表中。
  • 对所有类做重映射。
  • 将所有SEL都注册到namedSelectors表中。
  • 修复函数指针遗留。
  • 将所有Protocol都添加到protocol_map表中。
  • 对所有Protocol做重映射
  • 初始化所有⾮懒加载的类,进⾏rw、ro等操作。
  • 遍历已标记的懒加载的类,并做初始化操作。
  • 处理所有Category,包括Class和Meta Class。
  • 初始化所有未初始化的类。

5.非懒加载类的加载:
realizeClassWithoutSwift对非懒加载类进行加载,并给rw开辟内存空间,然后将ro的数据“拷⻉”到rw⾥⾯。递归调⽤realizeClassWithoutSwift,对⽗类和元类进⾏初始化。最后设置⽗类,isa指针的初始化。


文章标签:

原文连接:https://juejin.cn/post/7100494990089912327

相关推荐

Webpack学习系列 - Webpack5 怎么集成Babel ?

我在淘宝做弹窗,2022 年初的回顾与展望

看完这篇,你也可以搞定有趣的动态曲线绘制

低代码平台的属性面板该如何设计?

34个图片压缩工具集合,包含在线压缩和CLI工具

冴羽答读者问:过程比结果重要吗?如果是,怎么理解?如果不是,又怎么解?

中杯超大杯中间的新选择——vue2.7+vite+ts实践

LiveData源码分析

亚马逊Prime:流媒大战杀手锏

Vue详解知识概括

基于 Docker 来部署 Vue 或 React 前端项目及 Node 后端服务

完美解决自定义字体垂直方向上下偏移的问题

使用vuepress从零开始搭建我的技术文档|已部署到线上

【Vue.js 3.0源码】AST 转换之节点内部转换

小程序+电商,该如何寻找营销增长点

前端如何开始深度学习,那不妨试试JAX

你可能不知道的 前端浏览器(window) 对本地文件操作(File System Access API)

爱奇艺向抖音开启授权,打开内容价值的新大门

使用ComposeDesktop开发一款桌面端多功能APK工具

一个简洁、强大、可扩展的前端项目架构是什么样的?