一起养成写作习惯!这是我参与「掘金日新计划 · 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方法初始化全局变量或者静态变量。