说的比较直白,iOS 中有个好东西,都知道那就是分类Category,有了这个分类咱们可以轻松的给基类添加一些功能,更加灵活的添加咱们想要的功能。可是在使用的时候,咱们要注意一点,就是避免去复写父类的方法,若是不当心复写了父类方法,可能由此变得乱套了。bash
其实说白了,若是复写父类的方法,可能会引起父类方法的内容的变动,这样是极其危险的。为了验证这样的问题,特地写了一个分类:ui
#import "UIViewController+Add.h"
#import <objc/runtime.h>
@implementation UIViewController (Add)
- (void)viewDidLoad{
NSLog(@"分类 viewDidLoad");
}
复制代码
而主类中,咱们不导入这个分类,看一下运行结果:spa
#import "ViewController.h"
#import <objc/runtime.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(@"基类的 ViewDidLoad 方法");
id viewController = objc_getClass("ViewController");
int outCount,i;
Method *methodList = class_copyMethodList(viewController, &outCount);
for (i = 0; i < outCount; i++) {
Method method = methodList[i];
NSLog(@"current method is :%@",NSStringFromSelector(method_getName(method)));
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
复制代码
结果以下:3d
看一下你会发现,在没有导入分类头文件的状况下,分类中的方法被调用了。为了解释这个问题,咱们将 controller
中的方法用runtime 打印出来,确实只有两个方法。根据运行的结果,发现子类的 viewDidLoad
先被调用,其次父类的 viewDidLoad
被调用,仿佛在父类的viewDidLoad
中添加了分类的内容。这里有两点疑问:code
为了搞懂这两点,咱们继续往下深扒~cdn
从打印出来的方法咱们可以看出来ViewController
中确实只有一个 ViewDidLoad 方法,因此在methodLists
中只有一个与之对应的 SEL。其次,在没有引入头文件的状况下,可以自动调用分类中的方法,只能说明一点,那就是父类中的 ViewDidLoad
已经受到分类方法的影响,已经被分类复写,这里说判定是复写是由于在去除[super viewDidLoad]
以后,分类中的分类 viewLoad
提示已经不在了。blog
这里解释了第二点,分类复写的方法与父类的方法的关系是覆盖关系,分类方法覆盖父类的方法。继承
那么第一点在没有导入头文件的状况下,为何分类的方法会被引用呢?get
这一点其实也很好解释,由于不论有没有
import category
的头文件,均可以成功调用category
的方法,在runtime
加载成功以后,Category
已经将扩展中的复写的方法对原方法进行了替换,import只是帮助了编译检查和连接过程。关于runtime 对于 category 的加载流程咱们能够参考这篇文章objc category的秘密string
对于以上分析,总结来讲就是:
hook
方法进行;runtime
在加载完成以后会将分类的扩展方法加入到 methodList
方法列表中,导入头文件的过程只是保证编译检查成功以及连接过程顺利完成;在一个类中添加多个不一样分类的相同方法,以下:
#import "LCView+AddOne.h"
@implementation LCView (AddOne)
- (void)testMethod{
NSLog(@"from category one");
}
@end
复制代码
#import "LCView+AddTwo.h"
@implementation LCView (AddTwo)
- (void)testMethod{
NSLog(@"from category two");
}
@end
复制代码
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
id testView = objc_getClass("LCView");
unsigned int outCount,i;
Method *methodList = class_copyMethodList(testView, &outCount);
for (i = 0; i < outCount; i++) {
Method method = methodList[i];
NSLog(@"current method is :%@",NSStringFromSelector(method_getName(method)));
}
LCView *test = [LCView new];
[test testMethod];
}
复制代码
运行以后的结果以下:
可见,若是多个分类扩展添加同一个方法的话,当前methodList
中会同时有多个 SEL,而真正调用的时候调用的 SEL 是其中一个,这个调用顺序与源文件的编译顺序有关,他们的关系是根据buildPhases->Compile Sources
里面的顺序从上至下编译的,换句话说就是,越排在后面的分类方法将会被实际调用,如图:
这里分类AddTwo
里面的方法被调用了。