去年在网咖写的,还没写完。

背景
我在bugly里看到如下崩溃记录:

根据提示可以知道是数组取下标超出了其范围导致的崩溃。
bugly给出了导致崩溃代码的具体位置,做了相应处理后我觉得需要采取点措施杜绝这种因数组越界而导致的崩溃。
这篇文章就是记录我在解决这个问题的过程中遇到的一系列问题。
注:在这之前我对runtime的运用只是扩展属性。
两句代码重现这个崩溃
NSMutableArray *arr = [NSMutableArray array];
NSString *name = [arr objectAtIndex:8];
控制台信息:

阻止崩溃
方案一:try catch
大学时写C#经常用到try catch,在OC中我却几乎没用到这个东西,这次终于用上了。
try:可能异常的代码
catch:异常信息
finally:一定会执行的代码
代码:
NSMutableArray *arr = [NSMutableArray array];
NSString *name = nil;
@try {
name = [arr objectAtIndex:8];
} @catch (NSException *exception) {
NSLog(@"异常信息:%@", exception);
} @finally {
NSLog(@"name======%@",arr);
}
控制台信息:

这样虽然阻止了崩溃,但是要在每一个地方都这样写吗?显然是不合理的,我也没见过谁的代码大量使用try catch。所以关于try catch只是简单提一下,并未用于实际项目。
关于oc中的try catch,可参考:
为什么iOS开发中很少用到try catch
方案二:category
利用category覆盖原有的方法。
我要覆盖的是NSArray的这个方法:
- (ObjectType)objectAtIndex:(NSUInteger)index;
返回值ObjectType
是我没用过的,根据字面意思可以知道是:对象类型。其实自己都可想想象:OC数组只能装对象,所以通过objectAtIndex :
方法取出的也必然是对象。
现在就用category重写这个方法:

我把ObjectType换成id了,因为用ObjectType直接报错。这个方法刚把外壳写好就出现警告了,先不管,运行一下代码:
NSMutableArray *arr = [NSMutableArray array];
NSString *name = nil;
name = [arr objectAtIndex:8];
崩溃了,控制台信息如下:

我在category的那个方法里添加了一个断点,发现根本就没用走那个方法,但是我在使用objectAtIndex :
这个方法的地方按“cmd+鼠标右键”又跳转到了我写的那个category中,也就是说刚才写的这个category根本就没有覆盖系统的方法。
现在再来仔细看看那个警告:
Category is implementing a method which will also be implemented by its primary class
自我翻译:Category正在implement的方法将会被其基类implement。
翻译归翻译,还是要自觉谷歌下:
看这个
类族
此时的我很懵逼:NSArray的基类也有objectAtIndex:
方法?
我赶紧去看了下NSArray的头文件:

跟我记忆中的一样:NSArray继承自NSObject,NSObject会有
objectAtIndex:
方法?打死我都不信,连头文件我都懒得去看了。
回头看看控制台的信息:
-[__NSArrayM objectAtIndex:]: index 8 beyond bounds for empty array
我谷歌了一下__NSArrayM
,得到了如下信息:
1.这是一个私有API
2.是NSArray的父类
为了查看NSArray到底是什么东西,根据上面那个链接提供的方法object_getClass()
:


注:NSArray和NSMutableArray的基类不是同一个。
到现在我的问题是:
如何已非继承的方式重写NSArray的方法?对NSArray添加category覆盖原有方法不行,对__NSArrayM
这个私有类添加category显然更不行。
于是谷歌:
在这个过程中又引出一个概念:类簇
最终——
方案三:method swizzling
如何修改类的实现
在这之前,我对runtime的使用只是结合category给类添加属性,虽然平时也看了许多关于runtime的文章,但是,没有亲身体验是无法真正体会的。
今天这个机会来了,现在的目标就是:运用method swizzling将NSArray的objectAtIndex :
方法替换为我们自定义的方法。
关于load方法

__NSArrayI与__NSArray0
一般来说,
后续工作
成功阻止崩溃后就结束了吗?并没有。
调试模式下,我们希望尽可能的发现并找到代码中的问题。
+ (void)load {
#ifdef DEBUG // debug模式下让它崩溃
#else
// 不可变数组原本的方法
Method method_immutableArray_objectAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArray0"), @selector(objectAtIndex:));
// 不可变数组的安全方法
Method method_immutableArray_safeObjectAtIndex = class_getInstanceMethod(NSClassFromString(@"NSArray"),@selector(safeObjectAtIndex:));
// 交换方法
method_exchangeImplementations(method_immutableArray_objectAtIndex, method_immutableArray_safeObjectAtIndex);
#endif
}
为什么方法交换要写到load方法中?
method_exchangeImplementations执行两次的话,又交换回去了。
机智
我还没有用于公司的APP,因为我怕还有什么没有考虑周全的地方,所以先写篇文章分享出去让老司机审核下。
收获
aspect oriented programming 面向切面编程
为了解决项目中的一个问题,不放过一个懵逼点。
这就是传说中的:学以致用。
总结
遇到问题一定要死磕,在实际开发中遇到问题并解决比看十篇博客还有用。
知识点
last but not the least
。。。
未完待续
网友评论