美文网首页
[iOS] 数组防止插入nil,取值越界造成崩溃

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

作者: 田小北北 | 来源:发表于2017-09-13 17:02 被阅读628次

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

    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);
    

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

    相关文章

      网友评论

          本文标题:[iOS] 数组防止插入nil,取值越界造成崩溃

          本文链接:https://www.haomeiwen.com/subject/yqllsxtx.html