前言
- 在类的加载原理(中)里我们初步探讨了分类的加载流程,最后跟到了
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
:
- 此时还没有加载到分类内容,然后再继续走,在分类流程中,走到了
load_categories_nolock
方法中:
再打印category_t
对象cat
,和 cls内存
,就找到了分类App1
:
cls
地址和category_t
中的name
地址是一样的,说明App1
是WSPerson
的分类- 此时
instanceMethods
和classMethods
都有内容
2. 主类有,分类没有
流程
去掉分类的+load
方法,然后重新运行,得到主类和分类的流程如下:
-
主类流程如下:
_read_images
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
-
分类的流程:
main
->[WSPerson(App1) instanceMethodWithCategoryApp1]
查看ro
在主类的流程中
,走到realizeClassWithoutSwift
时,打印ro
:
- 此时居然
能加载分类的方法
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
方法中后:
总结
- 当分类和主类都有
+load
时:是经过attachCategories
加载分类信息的 - 分类和主类只有一个实现
+load
时,分类信息是通过data()
里面获取,也就是编译器做了处理 - 分类和主类都没有
+load
时,类通过第一次消息发送加载分类信息,也就是从data()
获取
主类可能因为业务需求要在
+load
中处理一些业务,但分类中也加上的话会增大系统的负荷,所以不建议在分类中使用+load
方法。
attachCategories
在上面的分析中attachCategories
和分类有关,再来分析下它,首先看看源码:
- 主要是
rwe
的properties
和protocols
调用attachLists
,传入的参数第一个是本类方法的指针,第二个参数是要增加的的数量
attachLists
再看看attachLists
源码,它主要是一个存储方法的算法:
主要流程如下:
-
- 当
!list && addedCount == 1
时,list = addedLists[0]
,由于addedLists
是指针的指针,所以addedLists[0]
是一个数组指针。
- 当
-
- 当有新增时,
oldCount = oldList ? 1 : 0
,如果oldList
没有值,会继续走这个方法。加入新增方法为2,现扩大内存,如果有oldList
,会将oldList
存在末尾,再将新方法从0号位置开始存
,直到存满。
- 当有新增时,
-
- 当有数组时,先根据新内存大小创建个新数组,然后再将
oldArray
数据从新数组的末尾开始存,存到addCount
处,然后再从0
开始存新的
- 当有数组时,先根据新内存大小创建个新数组,然后再将
流程图
attachLists
总体的流程如下:
通过分析我们知道新增的方法都在前面,这一点也印证了 当分类和主类方法重名时以分类为主的原因