前言

在开发中,我们可能会遇到一种情况,写了几百个vc,突然需求要在每个vc改个背景色,可你却没有写个BaseVC做基类,全都继承UIViewController,此时,身为程序员的你肯定不会做一个一个改背景色这个决定,就算是重新一个一个改继承,虽然一劳永逸但也是十分蛋疼的事情,要知道,category是个好东西,我们可以利用category来重写父类方法。

方法一

直接在category重写你要重写的方法,例如:

1
2
3
4
5
6
7
8
9
10
#import "UIViewController+Base.h"
@implementation UIViewController (Base)
- (void)viewDidLoad {
self.view.backgroundColor = [UIColor yellowColor];
}
@end

XCode此时会提示个警告:

1
Category is implementing a method which will also be implemented by its primary class

去除警告:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import "UIViewController+Base.h"
@implementation UIViewController (Base)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
- (void)viewDidLoad {
self.view.backgroundColor = [UIColor yellowColor];
}
#pragma clang diagnostic pop
@end

方法二

利用runtime交换方法

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
#import "UIViewController+Base.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation UIViewController (Base)
void MethodSwizzle(Class c, SEL orig, SEL new) {
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, new);
if (class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, newMethod);
}
}
+ (void)load {
MethodSwizzle([UIViewController class], @selector(viewDidLoad), @selector(myViewDidLoad));
}
- (void)myViewDidLoad {
self.view.backgroundColor = [UIColor yellowColor];
}
@end

为什么这里要判断下:

1
2
3
4
5
if (class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, newMethod);
}

而不直接method_exchangeImplementations呢?
因为这里有可能出现两种情况,一种是要替换的方法是父类的方法,作为子类的目标类并没有实现,另一种是要替换的方法目标类已经实现了。第一种情况的出现就是我们要判断的原因,class_addMethod是给类添加方法,返回值是是否添加成功,如果成功说明没有实现方法,就拿新方法替换掉刚添加的方法,否则则表示实现了方法,直接交换就好。当然你也可以在NSObject添加MethodSwizzle方法,方便其他类调用,向下面方法三一样。

方法三

也是利用运行时,手法略有不同,给NSObject添加个分类

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
38
39
40
41
42
43
44
#import "NSObject+Base.h"
#import <objc/runtime.h>
#define EXCHANGE_METHOD(a, b) [[self class] exchangeMethod:@selector(a) withNewMethod:@selector(b)]
@interface NSObject (Base)
+ (void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel;
@end
@implementation NSObject (Base)
+ (void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel {
Class class = [self class];
Method origMethod = class_getInstanceMethod(class, origSel);
Method newMethod = class_getInstanceMethod(class, newSel);
if (!origMethod) {
origMethod = class_getClassMethod(class, origSel);
}
if (!origMethod) {
// 手动添加方法
class_addMethod([self class], origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
origMethod = class_getInstanceMethod(class, origSel);
}
if (!origMethod) {
@throw [NSException exceptionWithName:@"Original method not found" reason:nil userInfo:nil];
}
if (!newMethod) {
newMethod = class_getClassMethod(class, newSel);
}
if (!newMethod) {
@throw [NSException exceptionWithName:@"New method not found" reason:nil userInfo:nil];
}
if (origMethod == newMethod) {
@throw [NSException exceptionWithName:@"Methods are the same" reason:nil userInfo:nil];
}
method_exchangeImplementations(origMethod, newMethod);
}
@end

然后在pch导入NSObject+Base,就可以在所有类使用啦,例如在上面的例子的UIViewController+Base

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@implementation UIViewController (Base)
+ (void)load {
EXCHANGE_METHOD(viewDidLoad, myViewDidLoad);
}
- (void)myViewDidLoad {
self.view.backgroundColor = [UIColor yellowColor];
}
@end

一样可以改背景色。

总结

第一种只是简单利用宏去掉警告,二三方法本质是相同的,利用runtime交换方法,看实际情况运用。