iOS常见Crash案例总结

下一篇见iOS Crash 捕获处理

  • 1、集合类相关崩溃
  • 2、找不到方法的实现unrecognized selector sent to instance
  • 3、KVC、KVO造成的crash
  • 4、EXC_BAD_ACCESS
  • 5、多线程中的崩溃

头文件和.m实现的文件公共部分代码


#import 

NS_ASSUME_NONNULL_BEGIN
/**
 代理协议协议是为了解决多个VC有同名同参数的方法而内部实现的逻辑不同,(如果内部逻辑都相同就可以抽取成单例了),
 所以为了避免在每个VC的.h 中都声明一个同名同参的方法显得冗余就将这些方法抽取出来封装成一个协议,
 各个VC遵循这个协议就等同于在.h 中声明了这些方法。
 但因为有些协议方法VC是不需要的,可以用@optional 标识
 */
@protocol PublicMethodProtocol 
- (void)requiredProtocolMethodA; //默认是@required

@optional
- (void)optionalProtocolMethodAB;
@end


@interface CrashCaseController : UIViewController
@property(nonatomic, weak) id delegate;
@end


@interface UnrecognizedSelectorVCObj : NSObject
- (void)onlyDefinMethod;
@end

@interface KvcKvoCrashVCObj : NSObject
@property (nonatomic, strong) NSString *age;
@end

@interface CrashCaseController (AssociatedObject)
@property (nonatomic, strong) UIView *associateView;
@end

NS_ASSUME_NONNULL_END


#import "CrashCaseController.h"
#import 
@implementation UnrecognizedSelectorVCObj

- (void)requiredProtocolMethodA {
    
}
- (void)onlyDefinMethod {
    
}
@end


@implementation KvcKvoCrashVCObj
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"value = %@, key = %@", value,key);
}

- (id)valueForUndefinedKey:(NSString *)key {
    return nil;
}

@end

@implementation CrashCaseController (AssociatedObject)
- (void)setAssociateView:(UIView *)associateView {
    objc_setAssociatedObject(self, @selector(associateView), associateView, OBJC_ASSOCIATION_ASSIGN);
    //objc_setAssociatedObject(self, @selector(associateView), associateView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *)associateView {
    return objc_getAssociatedObject(self, _cmd);;
}
@end


@interface CrashCaseController ()
@property(nonatomic, strong) NSMutableArray *mutableArray;
@property(nonatomic, strong) NSMutableArray *unsafeArray;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void(^blcok)(void);
@property (nonatomic, weak) UIView *weakView;
@property (nonatomic, unsafe_unretained) UIView *unSafeView;
@property (nonatomic, assign) UIView *assignView;

@end

@implementation CrashCaseController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self GCDCrashCase];
}
@end

场景一:集合类操作crash

crash原因:越界、添加nil、多线程非原子性操作、遍历的同时移除元素
解决方案:使用前用if(长度判断)、在读写元素时正确使用遍历方式

/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 场景一:集合类操作crash
 crash原因:越界、添加nil、多线程非原子性操作、遍历的同时移除元素
 解决方案:使用前用if(长度判断)、在移除元素时正确使用遍历方式
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)collectionCrash {
    [self collectionCrash1];
    [self collectionCrash2];
    [self collectionCrash3];
    [self collectionCrash4];
    [self collectionCrash5];
    [self collectionCrash6];
}
//1、字符串超长度
- (void)collectionCrash1 {
    NSString * string1 = @"0123456789";
    NSRange range1 = NSMakeRange(12, 1); //crash崩溃 :out of bounds
    NSLog(@"新字符串是:%@",[string1 substringWithRange:range1]);
}
//2、数组越界
- (void)collectionCrash2 {
    NSArray * nameArray = @[@"Roy", @"Mike", @"Jordan"];
    NSString * name = nameArray[3]; //crash崩溃 :index 3 beyond bounds [0 .. 2]'
    NSLog(@"name=%@",name);
}
//3、数组遍历的时候使用错误的方式移除元素
- (void)collectionCrash3 {
    NSMutableArray* array = [NSMutableArray array];
    [array addObject:@1];
    [array addObject:@2];
    [array addObject:@3];
    [array addObject:@4];
    [array addObject:@5];
    [array addObject:@6];
    /**
     * 遍历数组的方式大概有三种:for, for in, enumerateObjectsUsingBlock:。
     * 在这三种方法中,使用for和enumerateObjectsUsingBlock:迭代时,可以修改数组,不会有Crash等问题。
     */
    [array enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (obj.integerValue == 1) {
            [array removeObject:obj]; // 不崩溃
        }
    }];
    NSLog(@"array enumerate = %@",array);
    
    for (int i = 0; i < array.count; i++) {
        NSNumber *num = array[i];
        if (num.integerValue == 2) {
            [array removeObject:@2]; // 不崩溃
        }
    }
    NSLog(@"array for  = %@",array);
    
    for (NSNumber* obj in array) {
        if (obj.integerValue == 3) {
            [array removeObject:obj];//crash崩溃 :Collection <__NSArrayM: 0x2829946f0> was mutated while being enumerated
        }
    }
    NSLog(@"array for in = %@",array);
}
//4、数组插入nil
- (void)collectionCrash4 {
    NSString * name;
    NSMutableArray *mutableAry = [NSMutableArray arrayWithObjects:@"Roy",name, @"Mike", @"Jordan", nil];
    NSLog(@"nameArray=%@",mutableAry); // nameArray=(Roy) 对可变数组arrayWithObjects初始化遇到nil 会直接结束
    [mutableAry addObject:name]; //crash崩溃 : object cannot be nil'
}
//5、字典插入nil
- (void)collectionCrash5 {
    NSNumber *jordanAge;
    NSDictionary *ages = @{@"Roy":@22, @"Mike":@24, @"Jordan":jordanAge}; //crash崩溃 :attempt to insert nil object
    NSLog(@"ages=%@",ages);
}
//6、NSURL插入nil
- (void)collectionCrash6 {
    NSString * urString;
    NSURL * url = [[NSURL alloc] initFileURLWithPath:urString];//crash崩溃:nil string parameter'
    NSURL * url2 = [[NSURL alloc] initWithString:urString];//crash崩溃:nil string parameter'
    NSLog(@"%@ %@",url, url2);
}

场景二:Unrecognized Selector

crash原因:找不到方法iOS系统抛出异常崩溃
解决方案:
1、方法调用前进行 respondsToSelector 判断
2、在.h中声明的方法如果用不到就去掉,用得到就同时在.m文件中实现
3、给NSObject添加一个分类,实现消息转发的几个方法


/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 场景二:Unrecognized Selector
 crash原因:找不到方法iOS系统抛出异常崩溃
 解决方案:
 1、方法调用前进行 respondsToSelector 判断
 2、在.h中声明的方法如果用不到就去掉,用得到就同时在.m文件中实现
 3、给NSObject添加一个分类,实现消息转发的几个方法
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)unrecognizedSelectorCrash {
    [self unrecognizedSelectorCrash1];
    [self unrecognizedSelectorCrash2];
    [self unrecognizedSelectorCrash3];
    [self unrecognizedSelectorCrash4];
    [self unrecognizedSelectorCrash5];
}
//1、未实现.h声明的方法
- (void)unrecognizedSelectorCrash1 {
    UnrecognizedSelectorVCObj* obj = [[UnrecognizedSelectorVCObj alloc] init];
    [obj onlyDefinMethod]; //crash崩溃:unrecognized selector sent to instance
}
//2、未实现遵守协议中的方法
- (void)unrecognizedSelectorCrash2 {
    UnrecognizedSelectorVCObj* obj = [[UnrecognizedSelectorVCObj alloc] init];
    [obj requiredProtocolMethodA];//crash崩溃:unrecognized selector sent to instance
}
//3、未实现代理方法
- (void)unrecognizedSelectorCrash3 {
    UnrecognizedSelectorVCObj* obj = [[UnrecognizedSelectorVCObj alloc] init];
    self.delegate = obj;
    [self.delegate optionalProtocolMethodAB]; //crash崩溃:unrecognized selector sent to instance
}
//4、可变对象copy返回不可变对象后调用了可变对象方法
/*
 *crash崩溃:unrecognized selector sent to instance
 *@property (nonatomic, copy) NSMutableArray *mArray;
 等同于
 - (void)setMutableArray:(NSMutableArray *)mutableArray {
 _mutableArray = [mutableArray copy]; //copy返回的对象都是不可变的
 修改为 _mutableArray = [mutableArray mutableCopy]; //对可变对象mutableCopy是深拷贝返回的新对象是可变的
 }
 解决办法:使用strong修饰或者重写set方法
 */
- (void)unrecognizedSelectorCrash4 {
    NSMutableArray* array = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
    [array addObject:@4];
    self.mutableArray = array;
    [self.mutableArray addObject:@5];//crash崩溃:unrecognized selector sent to instance
}
//5、低版本系统使用高版本API
- (void)unrecognizedSelectorCrash5 {
    if (@available(iOS 10.0, *)) {
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            
        }];
    } else {
        // Fallback on earlier versions
    }
}
//注释该该方法测试crash
- (void)setMutableArray:(NSMutableArray *)mutableArray {
    _mutableArray = [mutableArray mutableCopy];
}

场景三:KVC、KVO造成的crash

KVC造成crash原因:给不存在的key(包括key为nil)设置value
解决方案:重写类的setValue:forUndefinedKey:和valueForUndefinedKey:
KVO造成crash原因: 观察者没有实现observeValueForKeyPath:ofObject:changecontext:方法:,会崩溃或多次移除观察者
解决方案:观察者需要实现observeValueForKeyPath方法,addObserver和removeObserver一定要成对出现

/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 场景三:KVC、KVO造成的crash
 KVC造成crash原因:给不存在的key(包括key为nil)设置value
 解决方案:重写类的setValue:forUndefinedKey:和valueForUndefinedKey:
 
 KVO造成crash原因: 观察者没有实现observeValueForKeyPath:ofObject:changecontext:方法:,会崩溃或多次移除观察者
 解决方案:观察者需要实现observeValueForKeyPath方法
 addObserver和removeObserver一定要成对出现,
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)KvcKvoCrashCase {
    [self KvcKvoCrashCase1];
    [self KvcKvoCrashCase2];
    [self KvcKvoCrashCase3];
    [self KvcKvoCrashCase4];
    [self KvcKvoCrashCase5];
}
//1、key为nil
- (void)KvcKvoCrashCase1 {
    KvcKvoCrashVCObj* obj = [[KvcKvoCrashVCObj alloc]init];
    NSString *value, *key;
    // value 为nil不会崩溃
    [obj setValue:value forKey:@"age"];
    // key为nil会崩溃
    [obj setValue:@"value" forKey:key];//crash崩溃:attempt to set a value for a nil key
}
//2、key不是obj的属性
- (void)KvcKvoCrashCase2 {
    KvcKvoCrashVCObj* obj = [[KvcKvoCrashVCObj alloc]init];
    [obj setValue:@"性别女" forKey:@"sex"];//crash崩溃:this class is not key value coding-compliant for the key age.
}
- (void)KvcKvoCrashCase3 {
    KvcKvoCrashVCObj* obj = [[KvcKvoCrashVCObj alloc]init];
    //1、没有有实现observeValueForKeyPath:ofObject:changecontext:方法:,会崩溃
    [obj addObserver:self
          forKeyPath:@"age"
             options:NSKeyValueObservingOptionNew
             context:nil];
    obj.age = @"18岁";//crash崩溃:An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
}
//2、重复添加观察者,不会崩溃,但是添加多少次,一次改变就会被观察多少次
- (void)KvcKvoCrashCase4 {
    KvcKvoCrashVCObj* obj = [[KvcKvoCrashVCObj alloc]init];
    [obj addObserver:self
          forKeyPath:@"age"
             options:NSKeyValueObservingOptionNew
             context:nil];
    [obj addObserver:self
          forKeyPath:@"age"
             options:NSKeyValueObservingOptionNew
             context:nil];
    
    obj.age = @"18岁";//crash崩溃:An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
}
//3、重复移除观察者,会崩溃
- (void)KvcKvoCrashCase5 {
    KvcKvoCrashVCObj* obj = [[KvcKvoCrashVCObj alloc]init];
    [obj addObserver:self
          forKeyPath:@"age"
             options:NSKeyValueObservingOptionNew
             context:nil];
    obj.age = @"18岁";
    [obj removeObserver:self forKeyPath:@"age"];
    [obj removeObserver:self forKeyPath:@"age"]; //crash崩溃:Cannot remove an observer for the key path "age"  because it is not registered as an observer.
}
//注释掉该方法测试crash
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"object = %@, keyPath = %@", object,keyPath);
}

场景四:EXC_BAD_ACCESS

crash原因:出现悬挂指针,对象没有被初始化,或者访问的对象被释放
解决方案:调用block的时候,做判断; 对象的属性使用正确的修饰方式(strong/weak/assign)

/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 场景四:EXC_BAD_ACCESS
 crash原因:出现悬挂指针,对象没有被初始化,或者访问的对象被释放
 解决方案:
 调用block的时候,做判断
 对象的属性使用正确的修饰方式(strong/weak)
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)exc_bad_access_Crash {
    [self exc_bad_access_Crash1];
    [self exc_bad_access_Crash2];
    [self exc_bad_access_Crash3];
}
//1、访问没有实现的blcok
- (void)exc_bad_access_Crash1 {
    self.blcok(); //crash崩溃:Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)
}
//2、对象没有被初始化
- (void)exc_bad_access_Crash2 {
    UIView* view = [UIView alloc];
    view.backgroundColor = [UIColor blackColor];
    [self.view addSubview:view];
}
//3、访问的对象已经被释放掉
- (void)exc_bad_access_Crash3 {
    {
        UIView* view = [[UIView alloc]init];
        view.backgroundColor = [UIColor blackColor];
        self.weakView = view;
        self.unSafeView = view;
        self.assignView = view;
        self.associateView = view;
    }
    // ARC下weak对象释放后会自动置nil,因此下面的代码不会崩溃
    [self.view addSubview:self.weakView];
    // 野指针场景一:unsafe_unretained修饰的对象释放后,不会自动置nil,变成野指针,因此下面的代码会崩溃
    [self.view addSubview:self.unSafeView];
    // 野指针场景二:应该使用strong/weak修饰的对象,却错误的使用assign修饰,释放后不会自动置nil
    [self.view addSubview:self.assignView];
    // 野指针场景三:给类添加添加关联变量的时候,类似场景二,应该使用OBJC_ASSOCIATION_RETAIN_NONATOMIC修饰,却错误使用OBJC_ASSOCIATION_ASSIGN
    [self.view addSubview:self.associateView];
}

场景五:多线程中的崩溃

crash原因:死锁、子线程中更新UI、多个线程同时释放一个对象
解决方案: 多线程遇到需要同步的时候,加锁,添加信号量等进行同步操作。一般多线程发生的Crash,会收到SIGSEGV信号,表明试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。

/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 场景五:多线程中的崩溃
 crash原因:死锁、子线程中更新UI、多个线程同时释放一个对象
 解决方案:
 多线程遇到需要同步的时候,加锁,添加信号量等进行同步操作。一般多线程发生的Crash,会收到SIGSEGV信号,
 表明试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)GCDCrashCase {
    [self GCDCrashCase1];
    [self GCDCrashCase2];
    [self GCDCrashCase3];
    [self GCDCrashCase4];
}
//1、 dispatch_group_leave比dispatch_group_enter执行的次数多
- (void)GCDCrashCase1 {
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_leave(group);//crash崩溃:Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
}
//2、在子线程更新UI,未crash但终端打印出Main Thread Checker: UI API called on a background thread: -[UIViewController view]
- (void)GCDCrashCase2 {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        self.view.backgroundColor = [UIColor redColor];//[UIView setBackgroundColor:] must be used from main thread only
    });
}
//3、多个线程同时释放一个对象
- (void)GCDCrashCase3 {
    self.unsafeArray = [[NSMutableArray alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        for (int i = 0; i < 1000; i ++) {
            self.unsafeArray = [[NSMutableArray alloc] init];
        }
    });
    for (int i = 0; i < 1000; i ++) {
        self.unsafeArray = [[NSMutableArray alloc] init];
    }
}
/**
 A线程先完成初始化并赋值(这个实例我们叫它a), 然后继续往后走到其他逻辑.而这时候, B线程开始做初始化并赋值(这个实例我们叫它b), handler将指向B线程初始化出来的对象. 而A初始化出来的实例a因为引用计数减少1(减少到0)而被释放. 但在A线程中, 代码还会尝试访问a所在的地址, 这个地址里的内容因为被释放而变得无法预测, 从而导致野指针.
 */
- (void)setUnsafeArray:(NSMutableArray *)unsafeArray {
    _unsafeArray = unsafeArray;//crash崩溃:Thread 1: EXC_BAD_ACCESS
    //    加锁解决
    //    @synchronized (self) {
    //        _unsafeArray = unsafeArray;
    //    }
}
// 4、多线程中的数组扩容、深复制
/**扩容:数组的地址已经改变,报错was mutated while being enumerated
深复制 数组内的对象被 其他线程释放,访问僵尸对象
[NSArray copy] 浅复制、[NSArray mutableCopy]深复制
[NSMutableArray copy]深复制 、[NSMutableArray mutableCopy]深复制
 */
-(void)GCDCrashCase4 {
    
    {
        NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];
        NSArray *copyArray = [array copy];
        NSMutableArray *mCopyArray = [array mutableCopy];
        NSLog(@"array = %p,copyArray = %p,mCopyArray = %p", array, copyArray, mCopyArray);
        //array = 0x600000140060,copyArray = 0x600000140060,mCopyArray = 0x600000f78720
    }
    {
        NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
        NSArray *copyArray = [array copy];
        NSMutableArray *mCopyArray = [array mutableCopy];
        NSLog(@"array = %p,copyArray = %p,mCopyArray = %p", array, copyArray, mCopyArray);
        //array = 0x600000f4cb70,copyArray = 0x600000f4e850,mCopyArray = 0x600000f4cf00
    }
    
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", 0);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", 0);
    
    NSMutableArray* mutableArray = [NSMutableArray array];
    
    dispatch_async(queue1, ^{
        while (true) {
            if (mutableArray.count < 100) {
                [mutableArray addObject:@(mutableArray.count)];
            }
            else {
                [mutableArray removeAllObjects];
            }
        }
    });
    
    dispatch_async(queue2, ^{
        // case 4:深复制 数组内的对象被 其他线程释放,访问僵尸对象
        // 在 [mutableArray copy] 的过程,copy 方法内部调用initWithArray:range:copyItems: 时
        // 数组被另一个线程清空,range 不一致导致抛出 exception
        while (true) {
            NSArray* immutableArray1 = [mutableArray copy];
            for (NSNumber* number in immutableArray1) {
                NSLog(@"%@", number);
            }
        }
    });
}

你可能感兴趣的:(iOS常见Crash案例总结)