学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

Category和load

问题

一个类的分类重写了这个类的方法后,那么这个类的这个方法将失效,那么原方法失效,分类方法生效的原因是什么?

+load

+load 方法是当类或分类被添加到 Objective-C runtime 时被调用的,实现这个方法可以让我们在类加载的时候执行一些类相关的行为。子类的 +load 方法会在它的所有父类的 +load 方法之后执行,而分类的 +load 方法会在它的主类的 +load 方法之后执行。但是不同的类之间的 +load 方法的调用顺序是不确定的。

1
2
原因就在这里,因为加载顺序是父类先+load,然后子类+load,然后分类+load,那么如果分类重写子类方法:首先子类+load,将方法添加到类的方法列表methodLists,然后分类+load,将重写的方法添加到方法列表中

但是这里存在几点疑问:

  1. 方法列表methodLists里是否会有两个SEL相同的方法?
  2. 如果会有,这两个方法在方法列表中的顺序是怎样的?(顺序决定哪个被调用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#import "TestCategory.h"
/*主类实现*/
@implementation TestCategory
- (void)newMethod {
NSLog(@"主类");
}
@end

#import "TestCategory+add.h"
/*分类一实现*/
@implementation TestCategory (add)
- (void)newMethod {
NSLog(@"分类一");
}
@end


#import <objc/runtime.h>
#import "TestCategory.h"
@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

id LenderClass = objc_getClass("TestCategory");
unsigned int outCount, i;
//获取实例方法列表
Method *methodList = class_copyMethodList(LenderClass, &outCount);
for (i=0; i<outCount; i++) {
Method method = methodList[i];
NSLog(@"instanceMethod:%@", NSStringFromSelector(method_getName(method)));
}

//调用一下
TestCategory *tCategory = [[TestCategory alloc]init];
[tCategory newMethod];
}

看输出:

1
2
3
2017-07-19 21:38:13.593 TestRuntimeProperty[27827:1306334] instanceMethod:newMethod
2017-07-19 21:38:13.593 TestRuntimeProperty[27827:1306334] instanceMethod:newMethod
2017-07-19 21:38:13.594 TestRuntimeProperty[27827:1306334] 分类一

可见:

  1. 方法列表里会存在两个SEL相同的方法。
  2. 实际调用时,调用的是分类添加的方法,即分类添加的方法在方法列表methodLists的这个数组的顶部

答案已经很明确了:

1
后+load的类的方法,后添加到方法列表,而这时的添加方式又是插入顶部添加,即[methodLists insertObject:category_method atIndex:0]; 所以objc_msgSend遍历方法列表查找SEL 对应的IMP时,会先找到分类重写的那个,调用执行。然后添加到缓存列表中,这样主类方法实现永远也不会调到。

多个category实现同一个方法

如果多个分类同时重写同一个方法,执行顺序又是怎样的呢?
对于多个分类同时重写同一个方法,Xcode在运行时是根据buildPhases->Compile Sources里面的顺序从上至下编译的,那么很明显就像子类和分类一样,后编译的后load,即后添加到方法列表,所以后编译的分类,方法会放到方法列表顶部,调用的时候先执行。

1
2
3
4
5
6
7
#import "TestCategory+addAgain.h"
/*分类二实现*/
@implementation TestCategory (addAgain)
- (void)newMethod {
NSLog(@"分类二");
}
@end

看输出:

1
2
3
4
2017-07-19 22:18:13.593 TestRuntimeProperty[28385:1331972] instanceMethod:newMethod
2017-07-19 22:18:13.593 TestRuntimeProperty[28385:1331972] instanceMethod:newMethod
2017-07-19 21:18:13.593 TestRuntimeProperty[28385:1331972] instanceMethod:newMethod
2017-07-19 22:18:13.594 TestRuntimeProperty[28385:1331972] 分类一
1
总结:类的加载顺序,决定方法的添加顺序,调用的时候,后添加的方法会先被找到,所以调用的始终是后加载的类的方法实现。

+initialize

+initialize 方法是在类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。也就是说 +initialize 方法是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的 +initialize 方法是永远不会被调用的。那这样设计有什么好处呢?好处是显而易见的,那就是节省系统资源,避免浪费。

load VS initialize

文章参考

  1. Category 重写方法的调用
  2. Objective-C +load vs +initialize