1、方法替换class_replaceMethod
- (void)viewDidLoad {
[super viewDidLoad];
[self test7];
}
void myRun() {
NSLog(@"--myRun");
}
- (void)test7 {
Person *person = [[Person alloc] init];
class_replaceMethod([Person class], @selector(run), (IMP)myRun, "v@:");
[person run];
}
可以使用block
class_replaceMethod([Person class], @selector(run), imp_implementationWithBlock(^{
NSLog(@"block--imp");
}), "v@:");
2、方法交换method_exchangeImplementations
- (void)viewDidLoad {
[super viewDidLoad];
[self test7];
}
- (void)test7 {
Person *person = [[Person alloc] init];
Method runMethod = class_getInstanceMethod([Person class], @selector(run));
Method testMethod = class_getInstanceMethod([Person class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);
[person run];
}
3、拦截按钮点击事件应用
#import "UIControl+Extension.h"
#import <objc/runtime.h>
@implementation UIControl (Extension)
+ (void)load {
NSLog(@"%s",__func__);
Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method method2 = class_getInstanceMethod(self, @selector(jh_sendAction:to:forEvent:));
method_exchangeImplementations(method1, method2);
}
- (void)jh_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
// 注意,这里不会产生递归调用,因为上面已经交换了selector的方法实现了
[self jh_sendAction:action to:target forEvent:event];
if ([self isKindOfClass:[UIButton class]]) {
NSLog(@"拦截到按钮点击");
}
}
@end
4、数组防止添加空对象
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str = nil;
NSMutableArray *arr = [NSMutableArray array];
[arr addObject:@"a"];
[arr addObject:@"b"];
[arr addObject:str];
NSLog(@"arr-%@",arr);
}
报错:
*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil’
- 思路:尝试通过runtime修改方法
+ (void)load {
NSLog(@"%s",__func__);
Method method1 = class_getInstanceMethod(self, @selector(addObject:));
Method method2 = class_getInstanceMethod(self, @selector(jh_addObject:));
method_exchangeImplementations(method1, method2);
}
- (void)jh_addObject:(id)anObject {
if (anObject == nil) {
NSLog(@"添加为空的");
} else {
[self jh_addObject:anObject];
}
}
看起来好像没问题,但是时间上还是会报错
*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil’
- 1、思考:
- 为什么还是会报错呢,通过打印可以看到,是有进入load方法的
- 注意到:[__NSArrayM insertObject:atIndex:] ,这个类是 __NSArrayM
- 实际上NSString、 NSDictonary、NSArray、NSMutableArray等,都是属于__NSArrayM的类簇
- 改进如下:
+ (void)load {
NSLog(@"%s",__func__);
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(addObject:));
Method method2 = class_getInstanceMethod(cls, @selector(jh_addObject:));
method_exchangeImplementations(method1, method2);
}
打印结果:
2022-01-18 11:43:05.169324+0800 super-interview[38418:4264979] +[NSMutableArray(Extension) load]
2022-01-18 11:43:05.294043+0800 super-interview[38418:4264979] 添加为空的
2022-01-18 11:43:05.294251+0800 super-interview[38418:4264979] arr-(
a,
b
)结论:交换成功
- 2、进一步思考
[__NSArrayM insertObject:atIndex:] 本来是调用add方法,却报错来到了insert方法
容易猜想到内部实现,最终是调用insert方法
为了统一处理add 和 insert方法,直接替换insert方法不是更好吗
进步一改进:
+ (void)load {
NSLog(@"%s",__func__);
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(jh_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
}
- (void)jh_insertObject:(id)anObject atIndex:(NSUInteger)index {
if (anObject == nil) {
NSLog(@"添加为空的");
} else {
[self jh_insertObject:anObject atIndex:index];
}
}
打印结果:
2022-01-18 11:46:24.681854+0800 super-interview[38561:4268440] +[NSMutableArray(Extension) load]
2022-01-18 11:46:24.799525+0800 super-interview[38561:4268440] 添加为空的
2022-01-18 11:46:24.799678+0800 super-interview[38561:4268440] arr-(
a,
b
)
- 结论:有时候看到的类不一定是类本身,可以通过报错打印结果,看实际上是什么类,然后调用runtime的API的时候,传参class的时候,就传那个类