iOS 底层原理 iOS 进阶之路

OC底层原理十七:类的加载(上) read_images &

2020-10-20  本文已影响0人  markhetao

OC底层原理 学习大纲

上一节,我们了解了dyld和objc的关联,但是 map_images是如何将镜像macho映射内存的呢?

dyld与objc关联

本节内容:

  1. _read_images结构分析
  2. 类的加载(上)
    2.1 readClass 读取类
    2.2 realizeClassWithoutSwift 实现类
    2.3 methodizeClass 整理类
  3. 懒加载非懒加载的区别

准备工作:


1. _read_images结构分析

打开objc4源码,找到_objc_init, 进入map_images

void _objc_init(void)
{
    ... ...
    // 注册
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

// 👇 进入map_images
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);
}
image.png

以下是_read_images源码结构:

image.png
  1. 条件控制进行一次的加载
  2. 修复预编译阶段的@selector的混乱问题
  3. 类处理
  4. 修复重映射一些没被镜像文件加载进来的类
  5. 修复一些消息
  6. 当类中有协议时:readProtocol
  7. 修复没被加载的协议
  8. 分类处理
  9. 实现非懒加载类
  10. 没被处理的类(优化哪些被侵犯的类)

这里内容较多,我们先抓重点:macho读取到内存,最重要的是类信息读取

image.png

我们进入readClass,详细了解内部功能👇


2. 类的加载(上)

@interface HTPerson : NSObject

@property (nonatomic, copy) NSString *ht_name;
@property (nonatomic, assign) int ht_age;

- (void)ht_func1;
- (void)ht_func3;
- (void)ht_func2;

+ (void)ht_classFunc;

@end

@implementation HTPerson

- (void)ht_func1 { NSLog(@"%s",__func__); };
- (void)ht_func3 { NSLog(@"%s",__func__); };
- (void)ht_func2 { NSLog(@"%s",__func__); };

+ (void)ht_classFunc { NSLog(@"%s",__func__); };

@end

2.1 readClass 读取类

小技巧:

  • 源码中加入类的判断语句精准定位来分析自己的类
image.png image.png image.png

参数mangledName 是当前类名:

  • 已实现的类,从内存读取;未实现的类,从machO读取
    image.png
image.png image.png

细节:

    1. 此处会将本类元类注册类表
    1. 当前类表中的本类和元类,都只有名字地址,数据还没写入

返回_read_images中,继续寻找与类相关步骤,发现第9步 实现非懒加载类第10步 没被处理的类(优化哪些被侵犯的类)都调用了realizeClassWithoutSwift函数,对类进行实现。

2.2 realizeClassWithoutSwift 实现类

我们在_read_images函数的第9步第10步分别加上测试代码:

     const char * mangledName = cls->mangledName();
     const char * HTPersonName = "HTPerson";
            
     if (strcmp(mangledName, HTPersonName) == 0) {
         printf("resolvedFutureClasses: 精准定位 %s \n", HTPersonName);
     }
image.png image.png

然而,尴尬的是... 都没有执行到 😂
(倔强的我,HTPerson类进入_read_images后,单步断点一步步的测试,确实发现HTPerson类没有实现)

思考: 此处是app启动前,dyld调用_objc_init,执行map_images,发现自定义的HTPerson没有加载

  • 如果你在开发中用过lazy懒加载,就应该能联想到苹果的设计机制。没错,此处也是懒加载模式
  • 当类没被调用时,我们只会存储类名类地址
  • 真正被调用时,会检查是否实现,未实现就会触发实现

是否是懒加载类,关键在于是否实现+load方法没实现+load方法是懒加载类实现了就是非懒加载类

+ (void)load { NSLog(@"%s",__func__); };

再次运行代码,按照上述流程,发现精准定位到了【9.实现非懒加载类】

image.png realizeClassWithoutSwift

Q:赋值后bits为何为空?

image.png
所以此刻赋值已成功,但是还未写入内存

ro的读取 :

image.png
WWDC2020视频Advancements in the Objective-C runtime有介绍,据苹果官方统计,只有10%的类需要动态修改方法,所以为了节约内存,没进行动态拓展的类,直接从macho读取数据。反之,用rwe记录动态修改的数据,并从rwe读取数据。

下面我们进一步探索methodizeClass 整理类

2.3 methodizeClass 整理类

123.png

此时rweNull,所有rwe条件判断后的赋值,都没进入

Q:rwe什么时候会被赋值

根据WWDC2020视频Advancements in the Objective-C runtime,我们知道rwe是当类的方法动态修改时,才会创建。 那什么时候会被动态修改呢?我们下一节会分析。

在分析rwe的创建之前,我们先详细讲解懒加载类非懒加载类区别


3. 懒加载和非懒加载的区别

懒加载类非懒加载类区别在于:是否实现了+load方法。

懒加载类的加载:

  • 测试代码中,移除+load方法的实现,加入HTPerosn调用:
int main(int argc, const char * argv[]) {
   @autoreleasepool {
      HTPerson * person = [HTPerson alloc]; // 加入HTPerson的调用
   }
   return 0;
}
  • 移除所有断点,在realizeClassWithoutSwift函数中,加入定位代码断点:
   const char * HTPersonName = "HTPerson";
   const char * mangledName = cls->mangledName();
   if(strcmp(mangledName, HTPersonName) == 0) {
       auto ht_ro = (const class_ro_t *)cls->data();
       auto ht_isMeta = ht_ro->flags & RO_META;
       if (!ht_isMeta) {
           printf("%s - 精准定位! - %s\n", __func__, mangledName);
       }
   }
  • 运行程序,断点精准定位到HTPerson类,查看左边堆栈信息
image.png
  • 这个流程是否非常熟悉? 😃 这就是之前我们详细分析过的objc_msgSend流程

  • APP启动后,我们手动调用了alloc方法,触发消息机制,在进入方法的慢速查找时,我们会现检测当前类是否已实现,如果没有实现,就调用realizeClassWithoutSwift进行实现。

image.png

懒加载类非懒加载类总结:


本节我们了解了map_images如何将镜像macho映射到内存中,分析类的加载,了解懒加载类非懒加载类的区别。

下一节,OC底层原理十八:类的加载(中) SEL & 分类的加载 我们将从分类的探索开始,深入了解整个流程。

上一篇 下一篇

猜你喜欢

热点阅读