日常开发中,经常会遇到一些难以发现原因的bug。
而runtime的方法交换可以给我们帮大忙,下面举两个容易理解的场景。
例如 :
NSURL *URL = [NSURL URLWithString:@"https://www.baidu.com/s?ie=UTF-8&wd=图片"];
创建出来的URL用于请求,请求不到数据,你以为是后台的问题,实际却是因为urlString中含有中文导致创建出来的NSURL对象为null
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *URL = [NSURL URLWithString:@"https://www.baidu.com/s?ie=UTF-8&wd=图片"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSLog(@"URL = %@",URL); // 当urlString字符串含有中文时,创建出来的NSURL对象为null
}
@end
如何使开发者更容易发现问题所在呢?我们可以在创建完NSURL之后判断一下是否为空,并输入提示信息。
但是我们不希望在每一处使用到该方法的地方写重复的判断代码。
怎么办?
来看看如何使用方法交换:
#import "NSURL+ExchangeImplementations.h"
#import <objc/runtime.h> // 使用方法交换需要引用此头文件
@implementation NSURL (ExchangeImplementations)
// 程序启动之前会执行此方法
+ (void)load {
// 获取系统类方法
Method URLWithString = class_getClassMethod(self, @selector(URLWithString:));
// 获取自定义类方法
Method cs_URLWithString = class_getClassMethod(self, @selector(cs_URLWithString:));
// 调用方法交换
method_exchangeImplementations(URLWithString, cs_URLWithString);
}
// 实现自定义方法,用来与系统方法URLWithString:交换方法实现
+ (instancetype)cs_URLWithString:(NSString *)URLString {
NSURL *URL = [NSURL cs_URLWithString:URLString]; // 方法交换后,cs_URLWithString: 会执行系统方法 URLWithString:
if (!URL) {
NSLog(@"URL为空");
}
return URL;
}
@end
例如还有一个场景,数组越界在我们程序中经常发生,如果没测出来,App上线了,小小的出错可就会影响一大批用户,事情是很严重的。
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *fruits = @[
@"apple",
@"banner",
@"orange",
];
// NSString *fruit = [fruits objectAtIndex:3];
NSString *fruit = fruits[3]; // 数组越界,程序会直接崩溃
NSLog(@"the 3th fruit is %@",fruit);
}
@end
为了将影响降到最低,我们可以利用runtime对NSArray的 objectAtIndex: 和 objectAtIndexedSubscript: 方法做一下安全处理:
#import "NSArray+ExchangeImplementations.h"
#import <objc/runtime.h>
@implementation NSArray (ExchangeImplementations)
// 程序启动之前会执行此方法
+ (void)load {
NSArray *methodNames = @[
@"objectAtIndex:",
@"objectAtIndexedSubscript:",
];
// 遍历需要做方法交换的方法名数组
for (NSString *methodName in methodNames) {
SEL originSEL = NSSelectorFromString(methodName);
// 自定义方法的方法名比系统方法的方法名前多了 "cs_"
SEL newSEL = NSSelectorFromString([@"cs_" stringByAppendingString:methodName]);
// 这里使用 objc_getClass("__NSArrayI") ,是因为 NSArray 是类族, objectAtIndexedSubscript: 是NSArray的子类 __NSArrayI 的实例方法
Class class = objc_getClass("__NSArrayI");
Method originMethod = class_getInstanceMethod(class, originSEL);
Method newMethod = class_getInstanceMethod(class, newSEL);
// 方法交换
method_exchangeImplementations(originMethod, newMethod);
}
}
// 实现自定义方法,用来与系统方法 objectAtIndex: 交换方法实现
- (id)cs_objectAtIndex:(NSUInteger)index {
// 增加安全判断,防止数组越界带来程序崩溃
if (index < self.count) {
return [self cs_objectAtIndex:index]; // 方法交换后, cs_objectAtIndex: 的方法实现会指向 objectAtIndex:
} else {
NSLog(@"数组越界了!index = %lu, count = %lu", index, self.count);
return nil; // 如果下标越界,直接返回nil
}
}
// 实现自定义方法,用来与系统方法 objectAtIndexedSubscript: 交换方法实现
- (id)cs_objectAtIndexedSubscript:(NSUInteger)idx {
// 增加安全判断,防止数组越界带来程序崩溃
if (idx < self.count) {
return [self cs_objectAtIndexedSubscript:idx]; // 方法交换后, cs_objectAtIndexedSubscript: 的方法实现会指向 objectAtIndexedSubscript:
} else {
NSLog(@"数组越界了!index = %lu, count = %lu", idx, self.count);
return nil; // 如果下标越界,直接返回nil
}
}
@end
上面的代码中涉及到类族的概念,类族是什么?这是runtime更底层的知识了,想了解更多可以参考二亮子的《为什么object_getClass(obj)与[OBJ class]返回的指针不同》
本文仅举两个例子介绍runtime的方法交换的使用方法及使用场景,什么时候需要使用到runtime的方法交换,这要看开发者如何合理地运用,希望大家能结合实际,举一反三。
网友评论