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

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

1. 替换系统方法

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

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


在说处理方式前先了解一下替换系统方法的runtime方法,具体实现原理可阅读本人转载的一边文章:[iOS]Objective-C 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下载

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值