数组越界这类的 Crash 是最简单的也是最容易出现,业务开发过程中很可能操作某个 NSArray 类型的对象时忘记判空或者忘记长度判断而造成数组越界崩溃。所以最好是在线上环境接入这类的 Crash 防护。当然,在开发环境下最好不要接入,避免纵容开发者出现这类遗忘判断的错误。
这类崩溃的防护方案无非就是 Hook 可能产生 Crash 的类的相关方法。之前有过一篇文章是讲这类防护的:从 SafeKit 看异常保护及 Method Swizzling 使用分析
但 SafeKit 并未 Hook 全可能出现 Crash 的类及其方法,尤其是 NSArray 类簇。
关于类簇这里是苹果官网文档:Class Clusters
以及 sunnyxx 在 从NSArray看类簇 文章里的说法:
Class Clusters(类簇)是抽象工厂模式在 iOS 下的一种实现,众多常用类,如 NSString,NSArray,NSDictionary,NSNumber 都运作在这一模式下,它是接口简单性和扩展性的权衡体现,在我们完全不知情的情况下,偷偷隐藏了很多具体的实现类,只暴露出简单的接口。
我们来仔细打印下看看:
1 | // NSArray |
以 NSArray 为例,他在 alloc 阶段生成的是 __NSPlaceholderArray
的中间对象,然后在 init 阶段给这个中间对象发消息,由它做工厂,生成真正的对象。其中 NSMutableArray 生成的都是 __NSArrayM
类型,M 代表的就是 Mutable。NSArray 则区分了数组里:包含 0 个对象时生成的是 __NSArray0
类型,包含 1 个对象生成的是 __NSSingleObjectArrayI
类型,包含多个对象时生成的是 __NSArrayI
类型。
NSDictionary 同样是类似的。那我们的防护方案里则是 Hook 全这些类型,比如 NSArray 的 Category:
1 | + (void)load { |
当然 NSArray、NSMutableArray、NSDictionary、NSMutableDictionary、NSString、NSMutableString、NSNumber 这些类都提供了跟多的方法,只要细心仔细的将他们全 Hook 掉就好了。当然实际开发中可能常用的就那么几个方法,Hook 那些就已经足够了。
线上接入了这类的防护之后要比前边的文章讲的 Unrecognized Selector Crash 和 EXC_BAD_ACCESS Crash 更容易造成业务逻辑的错乱,毕竟业务逻辑中不可避免的要用到大量的 NSArray、NSDictionary 类,可能在接入这类防护后会操成点击无响应或者页面卡死,有时候这种情况甚至比程序崩溃还让用户崩溃,所以也要看实际开发需要的取舍。在接入防护后尤其要做好堆栈收集,上报 Crash 的工作,及时解决掉问题。