iOS开发防止数组越界及添加空值的crash的扩展

释放双眼,带上耳机,听听看~!

本文主要是为了防止数组越界及添加到数组的值为nil的时候导致的程序crash,使用了两种方法来对NSArray进行扩展:

1. 替换系统方法

主要是对数组的如下两个方法进行的处理:

- (ObjectType)objectAtIndex:(NSUInteger)index;
- (void)addObject:(ObjectType)object;

 

在说处理方式前先了解一下替换系统方法的runtime方法,具体实现原理可阅读本人转载的一边文章:iOS开发 Method Swizzling,文章中对实现替换的原理进行了讲解,这里就不再赘述;

使用了 runtime 中的方法,需要导入头文件:

 

#import <objc/runtime.h>

这里,我对NSObject类进行了扩展NSObject+Until,加入了替换系统方法的一个类方法,其.h文件如下:

 

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface NSObject (Until)
/*! @method swizzleMethod:withMethod:error:
@abstract 对系统方法进行替换
@param oldSelector 想要替换的方法
@param newSelector 实际替换为的方法
@param error 替换过程中出现的错误,如果没有错误为nil
*/
+ (BOOL)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector error:(NSError **)error;
@end

.m文件为:

 

#import "NSObject+Until.h"
@implementation NSObject (Until)
+ (BOOL)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector error:(NSError **)error
{
Method originalMethod = class_getInstanceMethod(self, originalSelector);
if (!originalMethod) {
NSString *string = [NSString stringWithFormat:@" %@ 类没有找到 %@ 方法",NSStringFromClass([self class]),NSStringFromSelector(originalSelector)];          *error = [NSError errorWithDomain:@"NSCocoaErrorDomain" code:-1 userInfo:[NSDictionary dictionaryWithObject:string forKey:NSLocalizedDescriptionKey]];   return NO;
}
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
if (!swizzledMethod) {
NSString *string = [NSString stringWithFormat:@" %@ 类没有找到 %@ 方法",NSStringFromClass([self class]),NSStringFromSelector(swizzledSelector)];          *error = [NSError errorWithDomain:@"NSCocoaErrorDomain" code:-1 userInfo:[NSDictionary dictionaryWithObject:string forKey:NSLocalizedDescriptionKey]];   return NO;
}
if (class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)))
{
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
return YES;
}
@end

这个类主要是实现方法的替换,接下来是对NSArray和NSMutableArray的扩展

对于NSArray主要是替换系统的objectAtIndex:方法

在使用这些类的时候,系统会默认调用+(void)load方法,就是在此方法中实现方法的替换:

 

+(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool {
[objc_getClass("__NSArrayI") swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(lqq_objectAtIndex:) error:nil];
};
});
}

使用了dispatch_once_t,保证方法只替换一次即可;

objectAtIndex:是系统的方法,lqq_objectAtIndex:是自己定义的需要替换为的方法,其方法实现为:

 

- (id)lqq_objectAtIndex:(NSUInteger)index
{
if (index < self.count) {
return [self lqq_objectAtIndex:index];
}
return nil;//越界返回为nil
}

注意这里的写法,在load方法中进行替换后,此处的[self lqq_objectAtIndex:]并不会行成递归,实际会执行[self
objectAtIndex:];

这样完成后,在使用时比较方便,只需正常的使用系统的objectAtIndex:方法,实际上会执行lqq_objectAtIndex:方法,在这个方法里进行一些判断就行;

对于NSMutableArray,其objectAtIndex:方法处理方法和上面一样,下面说下addObject:方法的处理,其+load方法实现如下:

 

+(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool {
[objc_getClass("__NSArrayM") swizzleMethod:
@selector(objectAtIndex:) withMethod:@selector(lqq_objectAtIndex:) error:nil];
[objc_getClass("__NSArrayM") swizzleMethod:
@selector(addObject:) withMethod:@selector(lqq_addObject:) error:nil];
};
});
}

将系统的addObject:替换为lqq_addObject:方法:

 

-(void)lqq_addObject:(id)object {
if (!object || [object isKindOfClass:[NSNull class]]) {
[self lqq_addObject:@"kong"];
} else {
[self lqq_addObject:object];
}
}

这样在使用时,只需直接使用系统的方法,在编译时会替换为自定义的方法;

测试如下:

 

NSArray *array = @[@"a",@"d",@"f",@"g",@"t"];
//数组越界,但是不会crash
for (int i = 0; i < 10; i++) {
NSLog(@"array >>> %@",[array objectAtIndex:i]);
}
NSMutableArray *mArray = [[NSMutableArray alloc]initWithArray:array];
NSString *str = nil;
//添加空值,不会crash
[mArray addObject:str];
//可变数组越界,但是不会crash
for (int i = 0; i < 10; i++) {
NSLog(@"mArray >>> %@",[mArray objectAtIndex:i]);
}

输出结果如下:

 

2016-02-28 19:26:12.060 SafetyArrayDemo[6540:470270] array >>> a
2016-02-28 19:26:12.061 SafetyArrayDemo[6540:470270] array >>> d
2016-02-28 19:26:12.061 SafetyArrayDemo[6540:470270] array >>> f
2016-02-28 19:26:12.062 SafetyArrayDemo[6540:470270] array >>> g
2016-02-28 19:26:12.062 SafetyArrayDemo[6540:470270] array >>> t
2016-02-28 19:26:12.063 SafetyArrayDemo[6540:470270] array >>> (null)
2016-02-28 19:26:12.063 SafetyArrayDemo[6540:470270] array >>> (null)
2016-02-28 19:26:12.063 SafetyArrayDemo[6540:470270] array >>> (null)
2016-02-28 19:26:12.063 SafetyArrayDemo[6540:470270] array >>> (null)
2016-02-28 19:26:12.064 SafetyArrayDemo[6540:470270] array >>> (null)
2016-02-28 19:26:12.064 SafetyArrayDemo[6540:470270] mArray >>> a
2016-02-28 19:26:12.064 SafetyArrayDemo[6540:470270] mArray >>> d
2016-02-28 19:26:12.065 SafetyArrayDemo[6540:470270] mArray >>> f
2016-02-28 19:26:12.066 SafetyArrayDemo[6540:470270] mArray >>> g
2016-02-28 19:26:12.066 SafetyArrayDemo[6540:470270] mArray >>> t
2016-02-28 19:26:12.067 SafetyArrayDemo[6540:470270] mArray >>> kong
2016-02-28 19:26:12.067 SafetyArrayDemo[6540:470270] mArray >>> (null)
2016-02-28 19:26:12.067 SafetyArrayDemo[6540:470270] mArray >>> (null)
2016-02-28 19:26:12.067 SafetyArrayDemo[6540:470270] mArray >>> (null)
2016-02-28 19:26:12.067 SafetyArrayDemo[6540:470270] mArray >>> (null)

2.自定义方法

这种实现起来比较简单,只需要扩展一个方法就行:

 

- (id)lqqNew_objectAtIndex:(NSUInteger)index{
//    [super objectAtIndex:index];
if (index < self.count)
{
return self[index];
}
else
{
return nil;
}
}
-(void)lqqNew_addObject:(id)object {
if (!object || [object isKindOfClass:[NSNull class]]) {
[self addObject:@"kong"];
} else {
[self addObject:object];
}
}

在使用的时候,就不能再使用系统的方法了,而是直接调用自定义的方法;

测试如下:

 

NSArray *array = @[@"a",@"d",@"f",@"g",@"t"];
//数组越界,但是不会crash
for (int i = 0; i < 10; i++) {
NSLog(@"A222 >>> %@",[array lqqNew_objectAtIndex:i]);
}
NSMutableArray *mArray = [[NSMutableArray alloc]initWithArray:array];
NSString *str = nil;
//添加空值,不会crash
[mArray lqqNew_addObject:str];
//可变数组越界,但是不会crash
for (int i = 0; i < 10; i++) {
NSLog(@"mArray22 >>> %@",[mArray lqqNew_objectAtIndex:i]);
}

输出:

 

2016-02-28 19:31:12.291 SafetyArrayDemo[6615:474083] A222 >>> a
2016-02-28 19:31:12.293 SafetyArrayDemo[6615:474083] A222 >>> d
2016-02-28 19:31:12.294 SafetyArrayDemo[6615:474083] A222 >>> f
2016-02-28 19:31:12.294 SafetyArrayDemo[6615:474083] A222 >>> g
2016-02-28 19:31:12.295 SafetyArrayDemo[6615:474083] A222 >>> t
2016-02-28 19:31:12.296 SafetyArrayDemo[6615:474083] A222 >>> (null)
2016-02-28 19:31:12.296 SafetyArrayDemo[6615:474083] A222 >>> (null)
2016-02-28 19:31:12.298 SafetyArrayDemo[6615:474083] A222 >>> (null)
2016-02-28 19:31:12.301 SafetyArrayDemo[6615:474083] A222 >>> (null)
2016-02-28 19:31:12.302 SafetyArrayDemo[6615:474083] A222 >>> (null)
2016-02-28 19:31:12.302 SafetyArrayDemo[6615:474083] mArray22 >>> a
2016-02-28 19:31:12.302 SafetyArrayDemo[6615:474083] mArray22 >>> d
2016-02-28 19:31:12.303 SafetyArrayDemo[6615:474083] mArray22 >>> f
2016-02-28 19:31:12.303 SafetyArrayDemo[6615:474083] mArray22 >>> g
2016-02-28 19:31:12.303 SafetyArrayDemo[6615:474083] mArray22 >>> t
2016-02-28 19:31:12.303 SafetyArrayDemo[6615:474083] mArray22 >>> kong
2016-02-28 19:31:12.304 SafetyArrayDemo[6615:474083] mArray22 >>> (null)
2016-02-28 19:31:12.333 SafetyArrayDemo[6615:474083] mArray22 >>> (null)
2016-02-28 19:31:12.334 SafetyArrayDemo[6615:474083] mArray22 >>> (null)
2016-02-28 19:31:12.335 SafetyArrayDemo[6615:474083] mArray22 >>> (null)

方法一,实现复杂,使用时方便,还是直接使用系统方法就行;方法二,实现简单,使用时需要直接调用自定义方法,不能使用系统方法;两种方法各有利弊,使用效果是一样的,自主选择即可!!

完整Demo下载

人已赞赏
iOS文章

iOS APP Extension - Share

2020-1-26 10:24:17

iOS文章

iOS开发POP/Push时导航栏出现阴影

2020-1-26 11:51:49

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
有新消息 消息中心
搜索