iOS底层-类的加载原理(下)

755 阅读4分钟

前言

  • 类的加载原理(中)里我们初步探讨了分类的加载流程,最后跟到了attachCategories方法,但是+load方法会影响运行走到attachCategories"路径",那么+load在什么情况会影响呢,attachCategories具体做了什么呢?文本将一一探讨

分类和类搭配+load

在上篇文章,我们知道类加上+load是非懒加载类,而去掉+load是走懒加载类,那么分类和类搭配 +load呢?接下里分几种情况进行分析,先准备好代码objc4-812源码,添加WSPerson类和WSPerson+App1分类

// .h
@interface WSPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSString *nickName;

+ (void)sayNB;
- (void)sayABC;
- (void)sayBCD;

@end

// .m
@implementation WSPerson

+ (void)load {
    NSLog(@"🎈🎈🎈%s", __func__);
}
+ (void)sayNB {
    NSLog(@"%s", __func__);
}
- (void)sayABC {
    NSLog(@"%s", __func__);
}
- (void)sayBCD {
    NSLog(@"%s", __func__);
}

@end

// 分类.h
@interface WSPerson (App1)

- (void)instanceMethodWithCategoryApp1;
+ (void)classMethodWithCategoryApp1;

@end

// 分类.m
@implementation WSPerson (App1)

+ (void)load {}
- (void)instanceMethodWithCategoryApp1 {
    NSLog(@"%s", __func__);
}
+ (void)classMethodWithCategoryApp1 {
    NSLog(@"%s", __func__);
}
@end
  • 然后在main中调用:
WSPerson *p = [WSPerson alloc];
[p instanceMethodWithCategoryApp1];

1. 主类和分类都有

在上篇文章中分析走的地方加上限制条件是否为WSPerson,然后运行得到流程:

流程

  • 主类走的流程:_read_images -> realizeClassWithoutSwift -> methodizeClass -> attachToClass

  • 分类的流程:load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

查看ro

主类的流程中,走到realizeClassWithoutSwift时,在判断类的断点处打印ro

截屏2021-07-23 16.33.21.png

  • 此时还没有加载到分类内容,然后再继续走,在分类流程中,走到了load_categories_nolock方法中:

截屏2021-07-23 16.39.57.png
再打印category_t对象cat,和 cls内存,就找到了分类App1

截屏2021-07-23 16.44.41.png

  • cls地址和category_t中的name地址是一样的,说明App1WSPerson的分类
  • 此时instanceMethodsclassMethods都有内容

2. 主类有,分类没有

流程

去掉分类的+load方法,然后重新运行,得到主类和分类的流程如下:

  • 主类流程如下:_read_images -> realizeClassWithoutSwift -> methodizeClass -> attachToClass

  • 分类的流程:main -> [WSPerson(App1) instanceMethodWithCategoryApp1]

查看ro

主类的流程中,走到realizeClassWithoutSwift时,打印ro

截屏2021-07-23 17.03.14.png

  • 此时居然能加载分类的方法

3. 主类没有,分类有

流程

去掉主类的+load,在分类中添加+load,再运行得到如下流程:

  • 主类走的流程:_read_images -> realizeClassWithoutSwift -> methodizeClass -> attachToClass

  • 分类走的流程:main -> [WSPerson(App1) instanceMethodWithCategoryApp1]

查看ro

主类的流程中,走到realizeClassWithoutSwift时,打印ro,发现和第2种情况一样

4. 主类和分类都没有

流程

  • 主类走的流程:lookUpImpOrForward -> realizeAndInitializeIfNeeded_locked -> initializeAndLeaveLocked -> initializeAndMaybeRelock -> realizeClassMaybeSwiftAndUnlock -> realizeClassMaybeSwiftMaybeRelock -> realizeClassWithoutSwift -> methodizeClass -> attachToClass

  • 分类走的流程:main -> [WSPerson(App1) instanceMethodWithCategoryApp1]

查看ro

  • 此处走到realizeClassWithoutSwift打印ro结构也和第2种情况一样

5. 主类有,多个分类不全有

流程

  • 主类走的流程:_read_images -> realizeClassWithoutSwift -> methodizeClass -> attachToClass

  • 分类走的流程:load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

查看ro

  • 主类的流程中,断点走到realizeClassWithoutSwift打印ro,内容和前面的一样。在分类流程中,断点走到了load_categories_nolock方法中后: 截屏2021-07-23 23.09.28.png

总结

  • 当分类和主类都有+load时:是经过attachCategories加载分类信息的
  • 分类和主类只有一个实现+load时,分类信息是通过data()里面获取,也就是编译器做了处理
  • 分类和主类都没有+load时,类通过第一次消息发送加载分类信息,也就是从data()获取

主类可能因为业务需求要在+load中处理一些业务,但分类中也加上的话会增大系统的负荷,所以不建议在分类中使用+load方法。

attachCategories

在上面的分析中attachCategories和分类有关,再来分析下它,首先看看源码: 截屏2021-07-23 23.44.12.png

  • 主要是rwepropertiesprotocols调用attachLists,传入的参数第一个是本类方法的指针,第二个参数是要增加的的数量

attachLists

再看看attachLists源码,它主要是一个存储方法的算法:

截屏2021-07-23 23.50.27.png
主要流程如下:

    1. !list && addedCount == 1时,list = addedLists[0],由于addedLists是指针的指针,所以addedLists[0]是一个数组指针。
    1. 当有新增时,oldCount = oldList ? 1 : 0,如果oldList没有值,会继续走这个方法。加入新增方法为2,现扩大内存,如果有oldList,会将oldList存在末尾,再将新方法从0号位置开始存,直到存满。
    1. 当有数组时,先根据新内存大小创建个新数组,然后再将oldArray数据从新数组的末尾开始存,存到addCount处,然后再从0开始存新的

流程图

attachLists总体的流程如下:

截屏2021-07-24 13.34.39.png

通过分析我们知道新增的方法都在前面,这一点也印证了 当分类和主类方法重名时以分类为主的原因