前言
在开发中,我们可能会遇到一种情况,写了几百个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交换方法,看实际情况运用。