一、category重写主类方法
在项目中我们经常会使用category(类别)来为已有的类添加新的方法。我们知道,如果我们重写原有类的方法,则类别的方法会替换掉原有类的方法,并且类别中会有警告的信息
原方法失效,分类方法生效的原理是什么?
这里看一下类的初始化,首先oc是动态语言,建立在runtime 的基础上,同样类的初始化也是动态的,根类NSObject 的+load 和+initilize两个方法,用于类的初始化,我们这里要着重看的是+load方法:
+load 方法是当类或分类被添加到 Objective-C runtime 时被调用的,实现这个方法可以让我们在类加载的时候执行一些类相关的行为。子类的 +load 方法会在它的所有父类的 +load 方法执行之后执行,而分类的 +load 方法会在它的主类的 +load 方法执行之后执行。
原因就在这里,因为加载顺序是父类先+load,然后子类+load,然后分类+load,那么如果分类重写子类方法:首先子类+load,将方法添加到类的方法列表methodLists,然后分类+load,将重写的方法添加到方法列表中,但是这里存在几点疑问:
A. 方法列表methodLists里是否会有两个SEL相同的方法?
B. 如果会有,这两个方法在方法列表中的顺序是怎样的?(顺序决定哪个被调用)
到这里很多人会猜:methodList 里面会有两个SEL 相同的方法名,但执行其中一个
#import "likeObj.h"@implementation likeObj-(void)test {NSLog(@"原来的类");
}@end#import "likeObj+likeCate.h"@implementation likeObj (likeCate)-(void)test {NSLog(@"类别的类");
}
@end//使用Runtime 来查看方法#import "ViewController.h"
#import <objc/runtime.h>
#import "likeObj.h"
#import "likeObj+likeCate.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];likeObj * li = [[likeObj alloc]init];[li test];id obj = objc_getClass("likeObj");unsigned int count ,i;Method *methodList = class_copyMethodList(obj, &count);for ( i =0 ; i< count; i++) {Method method = methodList[i];NSLog(@"------%@",NSStringFromSelector(method_getName(method)));}}
@end
// 打印为
2017-11-16 20:06:16.671 categoryDemo[6420:2222139] 类别的类
2017-11-16 20:06:18.781 categoryDemo[6420:2222139] ------test
2017-11-16 20:06:19.417 categoryDemo[6420:2222139] ------test
由代码可以看出,猜想是对的
可见:
1. 方法列表里会存在两个SEL相同的方法。
2. 实际调用时,调用的是后添加的方法,即后添加的方法在方法列表methodLists的这个数组的顶部
后+load的类的方法,后添加到方法列表,而这时的添加方式又是插入顶部添加,即[methodLists insertObject:category_method atIndex:0]; 所以objc_msgSend遍历方法列表查找SEL 对应的IMP时,会先找到分类重写的那个,调用执行。然后添加到缓存列表中,这样主类方法实现永远也不会调到。
二、多个category实现同一个方法
多个分类同时重写同一个方法,执行顺序又是怎样的呢?
对于多个分类同时重写同一个方法,Xcode在运行时是根据buildPhases->Compile Sources里面的顺序从上至下编译的,就像子类和分类一样,后编译的后+ load,即后添加到方法列表,所以后编译的分类,方法会放到方法列表顶部,调用的时候先执行
(所以会先调用下面的类的方法,此时如果你鼠标拖动两个类别的顺序,会随着你拖动的顺序改变的)
总结一句话: 类的加载顺序,决定方法的添加顺序,调用的时候,后添加的方法会先被找到,所以调用的始终是后加载的类的方法实现。