上一篇介绍了Method Swizzling
的相关方法,也介绍了一些常用场景,今天就详细介绍一下如何使用Method Swizzling
来拦截系统常见崩溃,感受Method Swizzling
带来的真正魅力。
要拦截系统常见的崩溃,诸如数组越界,截取子串,插入空值等,就要先交换系统的方法,再去拦截系统抛出的异常信息,举个🌰,现在要截取数组越界的崩溃,看代码:
NSArray *array = @[@"1",@"2",@"3"];
NSLog(@"%@", array[3]);
正常情况下,这段代码是肯定会崩溃的,看控制台输出的崩溃信息:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 3 beyond bounds [0 .. 2]'
现在我们要拦截这个崩溃,首先在调用objectAtIndexdSubscript
方法之前(我们调用array[3]
本质上就是调用objectAtIndexdSubscript
),我们要先交换这个方法,看代码:
+ (void)ht_interceptArrayCrashCausedByIndexBeyondBounds {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class clsI = NSClassFromString(@"__NSArrayI");
[HTCrashReporter ht_swizzleInstanceMethodForClass:clsI
originalSelector:@selector(objectAtIndexedSubscript:)
swizzlingSelector:@selector(ht_objectAtIndexedSubscriptI:)];
});
}
- (id)ht_objectAtIndexedSubscriptI:(NSUInteger)idx {
id object = nil;
@try {
object = [self ht_objectAtIndexedSubscriptI:idx];
} @catch (NSException *exception) {
[HTCrashReporter ht_catchException:exception withCrashType:HTCrashTypeArrayIndexBeyondBounds];
} @finally {
return object;
}
}
这里有几点需要注意下:
- 第一点:交换方法需要写在+load方法里,而且要保证只调用一次(这里不赘述具体原因,有兴趣的小伙伴自行查找);
- 第二点:我们交换的是对象方法,所以应该调用
swizzleInstanceMethod
方法而不是swizzleClassMethod
方法; - 第三点:🌰中的数组是不可变数组,根据控制台输出信息中提示的是
__NSArrayI
(两道下划线),所以我们要替换的类是NSClassFromString(@"__NSArrayI")
; - 第四点:有的小伙伴可能会有疑问,为什么在交换的方法里还要调用当前方法,这不就死循环了吗?非也,这里看上去调用当前方法,但是实质上内部方法的实现是系统方法的实现,所以这里使用了
try catch
语句,因为数组越界,系统方法会抛出异常,这里捕获异常再做进一步处理,比如上传服务器。到这里就实现了拦截了数据越界引起的崩溃,程序不会闪退还是可以正常运行。
上面只是一个简单的🌰,HTCrashReporter做了NSString
,NSArray
,NSDictionary
,NSObject
常见崩溃拦截,欢迎各位大佬指正,不介意动动手指请赏个Star。