类的加载(下)

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

在上一篇文章类的加载(上)了解了到了_read_images大概流程,也详细讲解了readClass方法 技能回顾

map_images函数实现类加载处理标红.jpg
下面讲解 标红区域 因为讲的是类的加载,当类的加载了解清楚了 其他步骤 也就清晰可见

懒加载类和非懒加载类

首先我们看类加载处理这里的源码

类的加载处理源码注释
在源码注释里我们很清晰的看到了一句话 Realize non-lazy classes (for +load methods and static instances) 翻译过来 就是 实现非懒加载类(当实现了+load方法 或者 static instances)❓
带着疑问 我们修改源码 在 类的加载(上)我们曾在readClass里面也进行了修改 这次我们在 下面继续断点 并 修改源码目的针对性研究
截屏2020-10-20 下午1.31.55.png
非懒加载类环境流程

将LGPerson里的项目 添加上+load 运行 按照源码所说 此时它为非懒加载类


截屏2020-10-20 下午1.35.40.png

项目断在readClass 并继续过断点


截屏2020-10-20 下午1.37.20.png
截屏2020-10-20 下午1.40.48.png

我们看到确实来到了修改源码的断点 并即将进入 realizeClassWithoutSwift 函数 下面我们进行研究

realizeClassWithoutSwift

源码注释此方法的含义


截屏2020-10-20 下午1.47.10.png

解读

源码查看及解读 (由于代码太长分段解读)


截屏2020-10-20 下午1.57.22.png
第一步:读取data数据
第二步:递归调用 realizeClassWithoutSwift 完善 继承链
截屏2020-10-20 下午2.29.45.png
第三步:通过 methodizeClass 方法化类

源码注释此方法的含义



截屏2020-10-20 下午2.49.43.png

源码查看及解读


截屏2020-10-20 下午3.28.43.png

结果发现 rwe 为NULL 并未运行其 rwe为真的判断 为什么 留疑问

懒加载类环境流程

👆上面的流程我们都是在实现了load的情况下进行跟进的下面👇我们来屏蔽load使其变为懒加载环境


截屏2020-10-20 下午10.23.29.png

并在初始化LGPerson代码断言 运行


截屏2020-10-20 下午10.29.57.png
发现 在main函数之前调用的 mapimages -> _read_images -> readClass
在次向下走一步断点
截屏2020-10-20 下午10.43.28.png

进入到methodizeClass里查看调用栈


截屏2020-10-20 下午10.48.33.png
这不正是我们之前学习的消息慢速转发流程lookImpOrForward发起的调用嘛

总结

分类的加载流程

attachToClass

在methodlist方法主要是将分类添加到主类中,其源码实现如下


截屏2020-10-20 下午11.05.34.png

attachCategories

截屏2020-10-21 下午4.58.58.png

源码注释解析

  1. 将方法列表、属性和协议从类别附加到类中。
    假设所有cats中的类别都已加载并按加载顺序排序,
    先有最古老的类别。

  2. 只有少数类在启动期间有超过64个类别。
    这使用了一个小堆栈,并避免malloc。

    类别必须按正确的顺序添加,也就是从后到前。为了完成分块操作,我们从前向后迭代cats_list,向后构建本地缓冲区,并在块上调用attachLists。attachLists突出显示的
    列表,因此最终结果按照预期的顺序。

截屏2020-10-21 下午5.09.28.png

在这里我们看到了 rwe 赋值

截屏2020-10-21 下午5.19.08.png

点进去我们也看到了 attachLists方法 从0-1的过程也存在下面进行分析它

attachLists

截屏2020-10-21 下午5.40.21.png

经过分析 我们可以看到 attachLists 方法存在三种情况 下面为 rwe 方法列表 0 -1的过程


rwe方法列表 0-1

回到上面断点处 我们打印 rwe


截屏2020-10-21 下午6.24.57.png

此时没有走下面的时候 rwe的 methods 正是 本类也就是ro->baseMethods 数据的 copy
我们继续向下走


截屏2020-10-22 上午10.51.07.png

画图表示 这个算法


category存入 64个空间的 mlists
  1. 先存进之前开辟好的64个空间的栈里
  2. mlists + ATTACH_BUFSIZ - mcount 获取mlists的首地址+ 64 - 有几个分类 最终等于 这几个分类的真实地址。抹掉没用到的。
  3. 排序
  4. 存入到rwe的methods中.
    1). 从上面的0-1 过程 就是 开始创建 rwe 将本类 ro中的baseMethods存入rwe中 此时 attachlists 是 0-1 是一个 一纬数组。
    2). 这次再进入 就是一个 1对多 的过程。


    list ->many Lists

在下面是 多对多的逻辑示意图


截屏2020-10-22 下午1.15.14.png

总结

类的加载源码解析分析图

类加载处理.jpg

上面我们已经大概理解了类是如何从Mach-0 加载到内存中,下面我们来详细了解 分类 如何加载到类中,以及 类 + 分类搭配使用情况

分类的本质

通过 clang 以及 苹果官方文档 及 objc源码 我们都可以了解到分类的本质就是一个结构体 截屏2020-10-22 下午6.15.17.png

分类的调用时机

上面的流程分析了 attachCategories的源码干了点什么事情它主要是 是否存才 rwe 不存在 初始化rwe 及 将分类的信息 帖到 rwe中 下面是它的流程
realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories
上面支线我们已经跑通 下面来全局搜索 attachCategories 看哪里还用到了它经过我们的反推有了以下流程
load_images -> loadAllCategories -> load_categories_nolock -> attachCategories
示意图


截屏2020-10-22 下午6.29.28.png

类和分类搭配加载

首先配置环境主类 LGPerson

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;
@property (nonatomic, assign) int kc_age;
- (void)kc_instanceMethod1;
- (void)kc_instanceMethod3;
- (void)kc_instanceMethod2;
+ (void)kc_sayClassMethod;
@end

NS_ASSUME_NONNULL_END

#import "LGPerson.h"
@implementation LGPerson
+ (void)load{
    
}

- (void)kc_instanceMethod3{
    NSLog(@"%s",__func__);
}

- (void)kc_instanceMethod1{
    NSLog(@"%s",__func__);
}

- (void)kc_instanceMethod2{
    NSLog(@"%s",__func__);
}
+ (void)kc_sayClassMethod{
    NSLog(@"%s",__func__);
}
@end

分类1 LGPerson+LGA

#import "LGPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson (LGA)
- (void)cateA_1;
- (void)cateA_2;
- (void)cateA_3;
@end
NS_ASSUME_NONNULL_END
#import "LGPerson+LGA.h"
@implementation LGPerson (LGA)
+ (void)load{
    
}
- (void)kc_instanceMethod1{
    NSLog(@"%s",__func__);
}
- (void)cateA_2{
    NSLog(@"%s",__func__);
}
- (void)cateA_1{
    NSLog(@"%s",__func__);
}
- (void)cateA_3{
    NSLog(@"%s",__func__);
}
@end

分类2 LGPerson+LGB

#import "LGPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson (LGB)
- (void)cateB_1;
- (void)cateB_2;
- (void)cateB_3;
@end

NS_ASSUME_NONNULL_EN
#import "LGPerson+LGB.h"

@implementation LGPerson (LGB)
+ (void)load{
    
}

- (void)kc_instanceMethod1{
    NSLog(@"%s",__func__);
}

- (void)cateB_2{
    NSLog(@"%s",__func__);
}
- (void)cateB_1{
    NSLog(@"%s",__func__);
}
- (void)cateB_3{
    NSLog(@"%s",__func__);
}

@end

1.非懒加载类 + 非懒加载分类

截屏2020-10-23 下午1.09.56.png

在下面继续断点跟踪


截屏2020-10-23 下午2.07.18.png

1.发现 cats_count =1 此时循环一次 分类的名字 为LGA 将分类中的方法 存入mists 的最后一位 上面有对此详细的解答

  1. 将分类的方法 进行排序 并将 分类的方法存入到 rwe中 通过 attachLists方法 上面我们也有对attachLists算法的详细解答

我们断过上面 来到 1399 1400 方法 并将 1367 和1399 1400 断点取消


截屏2020-10-23 下午2.21.37.png

继续过断点操作 发现此时断点又断回了attachCategories 中的针对性代码区域


截屏2020-10-23 下午2.25.40.png
继续打开 1367 1399 1400 向下走
截屏2020-10-23 下午2.30.11.png

1.发现 cats_count =1 此时循环一次 分类的名字 为LGB 将分类中的方法 存入mists 的最后一位

  1. 将分类的方法 进行排序 并将 分类的方法存入到 rwe中 通过 attachLists方法

继续取消 1367 和1399 1400 断点 向下走 发现又回到了 realizeClassWithoutSwift


截屏2020-10-23 下午2.42.20.png

断点到针对性代码下面


截屏2020-10-23 下午2.46.18.png

继续过断点 发现再此处 cls->isRealized() 判断了一下是否实现 实现返回cls 并没有向下执行 又回到了 realizeClassWithoutSwift方法对性代码里


截屏2020-10-23 下午2.49.29.png
继续过断点 从判断 cls->isRealized() 直接返回cls 整个流程跑完了程序进入了main函数
截屏2020-10-23 下午2.53.34.png

注:删掉其他断点留下针对性调试的断点 我们下面还会用哦
总结:非懒加载类+ 非懒加载分类 : 首先map_image -> _read_images() -> realizeClassWithoutSwift -> methodizeClass ->attachToClass ->结束
在这条支线上并未对 分类进行什么操作 只是完成了类的加载
然后发起 load_image函数的回调 -> loadAllCategories ->load_categories_nolock -> attachCategories (在这里有多次调用有几个非懒分类会调用几次)将分类的数据贴到 rwe中,并再次发起了 realizeClassWithoutSwift(有几个非懒分类会调用几次)的调用 来判断类是否已经实现。

2.非懒加载类 + 懒加载分类

3.懒加载类 + 懒加载分类

总结流程: lookUpImpOrForward -> realizeClassMaybeSwiftMaybeRelock ->realizeClassWithoutSwift->methodizeClass->走完

4.懒加载类 + 非懒加载分类 (经测试 一个类拥有多个分类的情况下只要有大于等于两个分类实现load就会走下面流程)

*继续过断点 来到了 attachCategories


截屏2020-10-23 下午4.40.52.png

总结

  1. 懒加载类 + 非懒加载分类 调用 load_image函数的回调 -> prepare_load_methods ->realizeClassWithoutSwift ->methodizeClass -> attachToClass -> 并进入到 分类附加方法里 调用->attachCategories -> for循环 分类个数次数 ; 分类方法mlist 存入 mlists[64 - ++mcount ] 先进来的放在最后面 第二次进来的放在倒数第二 -> 1.将mlists方法 序列化 ;2.通过attachLists算法将 分类的方法 贴到rwe之中 -> realizeClassWithoutSwift 判断是否实现了cls实现返回 进入 main函数

懒加载类 + 非懒加载分类 + 懒加载分类(这种情况多个分类的情况下 有且只有一个分类为非懒加载才会走下面)

这种情况当由主类没有实现load 分类 两个其中的一个实现load 另一个不实现

*继续向下过断点 发现并未进入到 下方 attachCategories 取消断点继续运行 进入到了main

截屏2020-10-23 下午5.32.53.png

总结:懒加载类 + 非懒加载分类 + 懒加载分类 流程 map_images -> realizeClassWithoutSwift -> methodizeClass -> attachToClass ->进入main并未发现 attachCategories的调用

最后总结

我们将这几种情况的调用流程拿出来
1.非懒加载类 + 非懒加载分类
map_image -> _read_images() -> realizeClassWithoutSwift -> methodizeClass
load_image -> loadAllCategories ->load_categories_nolock -> attachCategories
2.非懒加载类 + 懒加载分类
map_image -> _read_images() -> realizeClassWithoutSwift -> methodizeClass
3.懒加载类 + 懒加载分类
lookUpImpOrForward -> realizeClassMaybeSwiftMaybeRelock ->realizeClassWithoutSwift->methodizeClass
4.懒加载类 + 非懒加载分类(多个分类大于等于两个实现load流程)
load_image -> prepare_load_methods ->realizeClassWithoutSwift ->methodizeClass -> attachToClass ->attachCategories
5.懒加载类 + 非懒加载分类 + 懒加载分类(多个分类有且只有一个分类实现load)
map_images ->_read_images()-> realizeClassWithoutSwift -> methodizeClass

可见load的非必须不要随意用 ,因为 在main函数之前会做很多很多事情。


截屏2020-10-23 下午5.43.43.png
上一篇下一篇

猜你喜欢

热点阅读