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

OC底层原理 学习大纲

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

OC底层原理十七:类的加载(上) read_images & 懒加载类_第1张图片
dyld与objc关联

本节内容:

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

准备工作:

  • 可编译的objc4-781源码: https://www.jianshu.com/p/45dc31d91000
  • dyld-750.6: https://opensource.apple.com/tarballs/dyld/

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);
}
  • 进入map_images_nolock,找到核心代码_read_images读取镜像:
OC底层原理十七:类的加载(上) read_images & 懒加载类_第2张图片
image.png

以下是_read_images源码结构:

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

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

  • 我们发现首先对进行操作的是第3步 类处理。主要函数是readClass类的读取:
OC底层原理十七:类的加载(上) read_images & 懒加载类_第4张图片
image.png

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


2. 类的加载(上)

  • 先加上测试代码,使用自定义HTPerson类,便于定位验证
@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 读取类

小技巧:

  • 源码中加入类的判断语句精准定位来分析自己的类
  • readClass函数内加入识别HTPerson的测试代码,在测试代码区域加入断点(下图仅在3196行加入断点),运行代码:
OC底层原理十七:类的加载(上) read_images & 懒加载类_第5张图片
image.png
  • 代码进入断点,确认是HTPerson类后,我们加断点运行(上图3227、3241行),发现没进类的读取和加载区域。
    我们单步执行,查看具体流程:
image.png
  • 发现进入了addNamedClass函数。
OC底层原理十七:类的加载(上) read_images & 懒加载类_第6张图片
image.png

参数mangledName 是当前类名:

  • 已实现的类,从内存读取;未实现的类,从machO读取
    OC底层原理十七:类的加载(上) read_images & 懒加载类_第7张图片
    image.png
  • 进入addNamedClass函数:
OC底层原理十七:类的加载(上) read_images & 懒加载类_第8张图片
image.png
  • 发现主要是将类名插入类表中,NXMapInsert内部有关于类名哈希表扩容插入算法等细节介绍。此处不做拓展。

  • 我们返回上一步,查看下一个addClassTableEntry函数:

OC底层原理十七:类的加载(上) read_images & 懒加载类_第9张图片
image.png

细节:

    1. 此处会将本类元类注册类表
    1. 当前类表中的本类和元类,都只有名字地址,数据还没写入
  • 我们发现第3步 类处理 readClass内部并没有完成类的加载,仅仅将类名类地址记录到类表中。

返回_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);
     }
OC底层原理十七:类的加载(上) read_images & 懒加载类_第10张图片
image.png
OC底层原理十七:类的加载(上) read_images & 懒加载类_第11张图片
image.png

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

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

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

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

  • 我们在HTperson类测试代码中加入+load的实现:
+ (void)load { NSLog(@"%s",__func__); };

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

OC底层原理十七:类的加载(上) read_images & 懒加载类_第12张图片
image.png
  • 进入realizeClassWithoutSwift内部:
realizeClassWithoutSwift

Q:赋值后bits为何为空?

OC底层原理十七:类的加载(上) read_images & 懒加载类_第13张图片
image.png

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

ro的读取 :

OC底层原理十七:类的加载(上) read_images & 懒加载类_第14张图片
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方法,让HTPerson类变成了非懒加载类,才进入了realizeClassWithoutSwift函数。

  • 那如果不实现+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类,查看左边堆栈信息
OC底层原理十七:类的加载(上) read_images & 懒加载类_第15张图片
image.png
  • 这个流程是否非常熟悉? 这就是之前我们详细分析过的objc_msgSend流程,

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

OC底层原理十七:类的加载(上) read_images & 懒加载类_第16张图片
image.png

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

  • 苹果系统默认所有类都是懒加载类,这样不占用启动时间,且不占用过多资源
    OC底层原理十七:类的加载(上) read_images & 懒加载类_第17张图片
    image.png

  • 本节回顾:


    image.png

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

  • 但是,关于rwe何时加载?分类的加载方式等,很多细节我们都没有深入探索。

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

附录:
Q: 我们知道+load方法main函数之前执行,那本类分类+load执行顺序不相干两个类执行顺序是怎样的呢?
A:给大家分享这位博主的分析:https://blog.csdn.net/TuGeLe/article/details/86599216

你可能感兴趣的:(OC底层原理十七:类的加载(上) read_images & 懒加载类)