[iOS] 数组防止插入nil,取值越界造成崩溃

前一阵子遇到了数组插入空导致的崩溃问题, 由于时间问题没有去彻底解决它, 今天补上.
看了下网上的解决办法, 大致都是黑魔法, 或者继承,并重写父类方法, 总的看来不是方法不管用,而是覆盖的不够全面,导致有些情况下,分类的保护并没有卵用.
具体原因我也不一一赘述了. 好多文章可能都是转载, 自己并没有实际测试过, 简单的交换下那几个方法其实还是不够的, 废话不多说直接上代码.

Demo Git 地址 https://github.com/wanyiyuan/NSArraySaveDemo.git

我用的也是分类
NSObject 分类

//.h
#import <Foundation/Foundation.h>
@interface NSObject (Runtime)
#pragma mark - 方法交换
- (void)swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector;
@end

//.m
#import "NSObject+Runtime.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>

@implementation NSObject (Runtime)

/**
键盘弹起,程序进入后台后,会崩溃,原因是app进入后台后,系统会调用数组分类中的方法,
导致访问了未识别的方法崩溃,暂时的解决手段是:让键盘退出第一响应者,关于这个bug
可以参考下网上的处理办法,比如对应的类加入MRC环境,但是由于我写的这套里边有将nil
变成NSNull来处理, 所以会出现改成MRC环境后,会出现一个访问未识别方法的崩溃信息, 
如果想用这套逻辑的话,我的做法就是暂时先退出键盘的响应者, 如果你舍弃将nil变成NSNull
插入到数组中的话,可以将涉及到的类变成MRC环境
*/
+(void)applicationWillResignActiveNotification:(NSNotification *)note{
    [[[UIApplication sharedApplication] keyWindow] endEditing:YES];
    //[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];
}

+(void)load{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActiveNotification:) name:UIApplicationWillResignActiveNotification object:nil];
}
#pragma mark - 方法交换
- (void)swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector{
    Class class = [self class];
    
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    BOOL didAddMethod = class_addMethod(class,
                                        originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        class_replaceMethod(class,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
@end

NSArray分类

//.h
#import <Foundation/Foundation.h>
@interface NSArray (Save)
@end

//.m
#import "NSArray+Save.h"
#import <objc/runtime.h>
#import "NSObject+Runtime.m"

@implementation NSArray (Save)

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        @autoreleasepool {
            /** 不可变数组 */
            //空
            [objc_getClass("__NSArray0") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(emptyObjectAtIndex:)];
            //非空
            [objc_getClass("__NSArrayI") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(anyObjectAtIndex:)];
            
            /** 可变数组 */
            [objc_getClass("__NSArrayM") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(mutableObjectAtIndex:)];
            [objc_getClass("__NSArrayM") swizzleMethod:@selector(insertObject:atIndex:) swizzledSelector:@selector(mutableInsertObject:atIndex:)];
            [objc_getClass("__NSArrayM") swizzleMethod:@selector(addObject:) swizzledSelector:@selector(mutableAddObject:)];
            
            /** 只有一个元素 */
            //数组中只有一个元素
            [objc_getClass("__NSSingleObjectArrayI") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(singleObjectIndex:)];
            [objc_getClass("__NSSingleObjectArrayI") swizzleMethod:@selector(insertObject:atIndex:) swizzledSelector:@selector(singleInsertObject:atIndex:)];
            [objc_getClass("__NSSingleObjectArrayI") swizzleMethod:@selector(addObject:) swizzledSelector:@selector(singleAddObject:)];
            
            /** 类方法创建的数组,插入空时,下面这两个会崩溃 */
            [objc_getClass("__NSPlaceholderArray") swizzleMethod:@selector(initWithObjects:count:) swizzledSelector:@selector(swizzInitWithObjects:count:)];
        }
    });
}

#pragma mark - 不可变
/**
 空:nil 或 count = 0
 */
- (id)emptyObjectAtIndex:(NSInteger)index{
    NSLog(@"数组 = nil 或者 count = 0 返回 nil %s",__FUNCTION__);
    return nil;
}

/**
 多个元素
 */
- (id)anyObjectAtIndex:(NSInteger)index{
    if (index >= self.count || index < 0) {
        NSLog(@"取值时: 索引越界,返回 nil %s",__FUNCTION__);
        return nil;
    }
    id obj = [self anyObjectAtIndex:index];
    if ([obj isKindOfClass:[NSNull class]]) {
        NSLog(@"取值时: 取出的元素类型为 NSNull 类型,返回 nil %s",__FUNCTION__);
        return nil;
    }
    return obj;
}

#pragma mark - 两个类方法引起的崩溃
/**
 arrayWithObject
 arrayWithObjects:nil count:1
 */
-(id)swizzInitWithObjects:(const id [])objects count:(NSUInteger)cnt{
    for (int i=0; i<cnt; i++) {
        if (objects == NULL){
            NSLog(@"objects 为 NULL, 返回 nil %s",__FUNCTION__);
            return nil;
        }
        if (objects[i] == nil){
            NSLog(@"取值时: 取出的元素为 nil, 返回 nil %s",__FUNCTION__);
            return nil;
        }
    }
    return [self swizzInitWithObjects:objects count:cnt];
}

#pragma mark - 可变数组
/**
 取值
 */
- (id)mutableObjectAtIndex:(NSInteger)index{
    if (index >= self.count || index < 0) {
        NSLog(@"取值时: 索引越界, 返回 nil %s",__FUNCTION__);
        return nil;
    }
    id obj = [self mutableObjectAtIndex:index];
    if ([obj isKindOfClass:[NSNull class]]) {
        NSLog(@"取值时: 取出的元素类型为 NSNull, 返回 nil %s",__FUNCTION__);
        return nil;
    }
    return obj;
}

/**
 插入
 */
- (void)mutableInsertObject:(id)object atIndex:(NSUInteger)index{
    if (object) {
        [self mutableInsertObject:object atIndex:index];
    }else{
        NSLog(@"插入值时: 元素类型为 nil, %s",__FUNCTION__);
        [self mutableInsertObject:[NSNull null] atIndex:index];
    }
}

-(void)mutableAddObject:(id)object{
    if (object) {
        [self mutableAddObject:object];
    }else{
        NSLog(@"插入值时: 元素类型为 nil, %s",__FUNCTION__);
        [self mutableAddObject:[NSNull null]];
    }
}


#pragma mark - 数组中只有一个元素时三个方法不分可变和不可变
/**
 取值
 */
- (id)singleObjectIndex:(NSInteger)index{
    if (index >= self.count || index < 0) {
        NSLog(@"数组中只有一个元素, 取值时: 索引越界, 返回 nil %s",__FUNCTION__);
        return nil;
    }
    id obj = [self singleObjectIndex:index];
    if ([obj isKindOfClass:[NSNull class]]) {
        NSLog(@"数组中只有一个元素, 取值时: 元素类型为 NSNull 类型, 返回 nil %s",__FUNCTION__);
        return nil;
    }
    return obj;
}

/**
 插入
 */
- (void)singleInsertObject:(id)object atIndex:(NSUInteger)index{
    if (object) {
        [self singleInsertObject:object atIndex:index];
    }else{
        //数组中有一个元素时,判断下真实类型,如果是NSArray,则不添加
        Class superClass = self.superclass;
        NSString *superClassStr = NSStringFromClass(superClass);
        if (![superClassStr isEqualToString:@"NSArray"]) {
            NSLog(@"数组中只有一个元素, 并且数组真实类型为NSMutableArray 插入值: 元素类型为 nil, %s",__FUNCTION__);
            [self singleInsertObject:[NSNull null] atIndex:index];
        }else{
            NSLog(@"真实类型是NSArray,什么都不做 %s",__FUNCTION__);
        }
    }
}

-(void)singleAddObject:(id)object{
    if (object) {
        [self singleAddObject:object];
    }else{
        //数组中有一个元素时,判断下真实类型,如果是NSArray,则不添加
        Class superClass = self.superclass;
        NSString *superClassStr = NSStringFromClass(superClass);
        if (![superClassStr isEqualToString:@"NSArray"]) {
            NSLog(@"数组中只有一个元素, 并且数组真实类型为NSMutableArray 插入值: 元素类型为 nil, %s",__FUNCTION__);
            [self singleAddObject:[NSNull null]];
        }else{
            NSLog(@"真实类型是NSArray,什么都不做 %s",__FUNCTION__);
        }
    }
}
@end

测试代码,如果你想测试的话,自己简单的创建个文中用到的Person类

/** 不可变数组 */
    //角标越界
    //数组 = nil
    NSArray *array = nil;
    NSLog(@"array[1] = %@",array[2]);
    
    //count = 0
    NSArray *array1 = @[];
    NSLog(@"array1[1] = %@",array1[2]);
    
    //只有一个元素
    NSArray *array2 = @[@"123"];
    NSLog(@"array2[2] = %@",array2[2]);
    
    //多个元素
    NSArray *array3 = @[@"123",@"321",@"456"];
    NSLog(@"array3[8] = %@",array3[8]);
    
    //创建数组传入nil
    NSArray *array4 = [NSArray arrayWithContentsOfURL:nil];
    NSArray *array5 = [NSArray arrayWithObjects:nil, nil];
    NSArray *array6 = [NSArray arrayWithArray:nil];
    NSArray *array7 = [NSArray arrayWithObject:nil];
    NSArray *array8 = [NSArray arrayWithObjects:nil count:1];
    
    
    /**
     * 可变指针 指向 不可变数组
     * 此种指向需要注意,如果用不可变数组对象,访问了可变数组的方法会造成崩溃
     * 下面的数组其真实类型是不可变数组
     */
    NSMutableArray *arrayI = nil;
    NSLog(@"arrayI[1] = %@",arrayI[2]);
    [arrayI addObject:nil];
    [arrayI insertObject:nil atIndex:0];
    
    
    //count = 0
    NSMutableArray *array1I = @[];
    NSLog(@"array1I[1] = %@",array1I[2]);
    //[array1I addObject:nil];//语法错误,不可变访问可变方法,崩溃
    //[array1I insertObject:nil atIndex:0];//语法错误,不可变访问可变方法,崩溃
    
    //只有一个元素
    NSMutableArray *array2I = @[@"123"];
    NSLog(@"array2I[2] = %@",array2I[2]);
    
    //多个元素
    NSMutableArray *array3I = @[@"123",@"321",@"456"];
    NSLog(@"array3I[8] = %@",array3I[8]);
    
    //创建数组传入nil
    NSMutableArray *array4I = [NSArray arrayWithContentsOfURL:nil];
    NSMutableArray *array5I = [NSArray arrayWithObjects:nil, nil];
    NSMutableArray *array6I = [NSArray arrayWithArray:nil];
    NSMutableArray *array7I = [NSArray arrayWithObject:nil];
    NSMutableArray *array8I = [NSArray arrayWithObjects:nil count:1];
    
    
    /**
     * 可变指针 指向 可变数组
     */
    NSMutableArray *arrayM = nil;
    [arrayM addObject:nil];
    [arrayM insertObject:nil atIndex:3];
    
    NSMutableArray *array1M = [NSMutableArray array];
    [array1M addObject:nil];
    [array1M insertObject:nil atIndex:0];
    
    
    NSMutableArray *array2M = [[NSMutableArray alloc] init];
    [array2M addObject:nil];
    [array2M insertObject:nil atIndex:1];
    
    NSMutableArray *array3M = [NSMutableArray arrayWithObject:@"123"];
    [array3M addObject:nil];
    [array3M insertObject:nil atIndex:1];
    
    NSMutableArray *array16M = [NSMutableArray arrayWithContentsOfURL:nil];
    NSMutableArray *array18M = [NSMutableArray arrayWithObjects:nil, nil];
    NSMutableArray *array19M = [NSMutableArray arrayWithArray:nil];
    NSMutableArray *array14M = [NSMutableArray arrayWithObject:nil];
    NSMutableArray *array15M = [NSMutableArray arrayWithObjects:nil count:1];
    
    //测试插入对象为空
    NSMutableArray *array20M = [NSMutableArray array];
    [array20M addObject:nil];
    [array20M addObject:[[Person alloc]init]];
    [array20M addObject:[[Person alloc]init]];
    NSLog(@"array16M = %@",array20M);
    id obj = array20M[0];
    if ([obj isKindOfClass:[NSNull class]]) {
        NSLog(@"我是个null");
    }
    
    Person *p1 = array20M[0];
    NSLog(@"p1.name = %@",p1.name);

好了,就这么多, 谢谢支持

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容