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

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);
}
  • 进入map_images_nolock,找到核心代码_read_images读取镜像:
image.png

以下是_read_images源码结构:

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

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

  • 我们发现首先对进行操作的是第3步 类处理。主要函数是readClass类的读取:
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行加入断点),运行代码:
image.png
  • 代码进入断点,确认是HTPerson类后,我们加断点运行(上图3227、3241行),发现没进类的读取和加载区域。
    我们单步执行,查看具体流程:
image.png
  • 发现进入了addNamedClass函数。
image.png

参数mangledName 是当前类名:

  • 已实现的类,从内存读取;未实现的类,从machO读取
    image.png
  • 进入addNamedClass函数:
image.png
  • 发现主要是将类名插入类表中,NXMapInsert内部有关于类名哈希表扩容插入算法等细节介绍。此处不做拓展。

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

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);
     }
image.png
image.png

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

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

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

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

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

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

image.png
  • 进入realizeClassWithoutSwift内部:
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方法,让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类,查看左边堆栈信息
image.png
  • 这个流程是否非常熟悉? 😃 这就是之前我们详细分析过的objc_msgSend流程

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

image.png

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

  • 苹果系统默认所有类都是懒加载类,这样不占用启动时间,且不占用过多资源
    image.png

  • 本节回顾:


    image.png

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

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355