一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情。
异常产生
日常开发中,我们会遇到各种Crash
的出现。其中较为常见的,例如:数组越界
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong) NSArray *arr;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_arr = @[@"kc", @"hk", @"kd", @"cat"];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSString *str = [self.arr objectAtIndex:4];
NSLog(@"str:%@", str);
}
@end
-------------------------
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** __boundsFail: index 4 beyond bounds [0 .. 3]'
terminating with uncaught exception of type NSException
常规处理
解决方式也比较简单,创建一个NSArray
的分类,使用MethodSwizzling
将objectAtIndex
和自定义方法进行交互,在自定义方法中,增加索引判断,从而避免数组越界。
#import "NSArray+Extension.h"
#import <objc/runtime.h>
@implementation NSArray (Extension)
+ (void)initialize
{
if (self == [NSArray class]) {
Method method1 = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndex:));
Method method2 = class_getInstanceMethod(self, @selector(lg_objectAtIndex:));
method_exchangeImplementations(method1, method2);
}
}
- (id)lg_objectAtIndex:(NSUInteger)index{
if(index < self.count){
return [self lg_objectAtIndex:index];
}
NSLog(@"数组越界:index = %lu", index);
return nil;
}
@end
- 被替换的
objectAtIndex:
方法在__NSArrayI
中- 在
iOS
的Foundation
框架中,类簇是一种常用的设计模式,他将一些相近的,私有的,具体的子类组合在一个实体的抽象类下面,我称这个抽象类为实体的,是因为和我们交互的接口承载者,就是这个抽象大类 - 我们平时常用的三大类,
NSString
、NSArray
、NSDictionary
都是类簇,我们通过他们创建的对象都是其子类对象的实例化,并不是他本身的实例化
- 在
一般来说方法交换都会在load
方法中进行
- 因为
load
方法执行时机早,在main
函数之间就会调用,并且会主动调用 - 所有本类和分类中的
load
方法都会调用,父类优先于子类,本类优先于分类 load
方法的弊端,在objc_init
→load_images
函数中被调用,影响启动速度,让原本的懒加载类在启动时刻被迫营业。
如果使用initialize
方法代替,该方法在首次接收消息时自动调用,也能达到相同的效果,并且可以保持类的懒加载特性。
initialize
方法的特点:
initialize
方法属于被动调用的方法。原则上来说,会在main
函数之后触发。但也有特殊情况,当A类
在load
中调用B类
的方法,会触发B类
的initialize
方法,此时B类
的initialize
在main
函数之前执行。- 当子类首次接收消息时:
- 父类、子类都有,先调用父类的,再调用子类的
- 子类没有父类有,执行父类的方法。原则上一个类只会调用一次
initialize
方法,但这种情况父类的initialize
会执行多次,可以通过添加if(self == [ClassName self])
来进行判断 - 父类没有子类有,执行子类的方法
- 分类和本类都有,本类方法会被分类覆盖。多分类看文件的编译顺序,最后编译的分类中的方法会被执行。
由于分类会覆盖本类的initialize
方法,所以在initialize
中进行方法交互会存在一定风险。所以还是推荐load
方法内部实现Method Swizzle
,initialize
方法初始化全局变量或者静态变量。