iOS常見Crash案例總結

下一篇見iOS Crash 捕獲處理

  • 1、集合類相關崩潰
  • 2、找不到方法的實現(xiàn)unrecognized selector sent to instance
  • 3、KVC、KVO造成的crash
  • 4、EXC_BAD_ACCESS
  • 5、多線程中的崩潰

頭文件和.m實現(xiàn)的文件公共部分代碼


#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
/**
 代理協(xié)議協(xié)議是為了解決多個VC有同名同參數的方法而內部實現(xiàn)的邏輯不同,(如果內部邏輯都相同就可以抽取成單例了),
 所以為了避免在每個VC的.h 中都聲明一個同名同參的方法顯得冗余就將這些方法抽取出來封裝成一個協(xié)議,
 各個VC遵循這個協(xié)議就等同于在.h 中聲明了這些方法。
 但因為有些協(xié)議方法VC是不需要的,可以用@optional 標識
 */
@protocol PublicMethodProtocol <NSObject>
- (void)requiredProtocolMethodA; //默認是@required

@optional
- (void)optionalProtocolMethodAB;
@end


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


@interface UnrecognizedSelectorVCObj : NSObject<PublicMethodProtocol>
- (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 <objc/runtime.h>
@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<NSNumber*>* 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系統(tǒng)拋出異常崩潰
解決方案:
1、方法調用前進行 respondsToSelector 判斷
2、在.h中聲明的方法如果用不到就去掉,用得到就同時在.m文件中實現(xiàn)
3、給NSObject添加一個分類,實現(xiàn)消息轉發(fā)的幾個方法


/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 場景二:Unrecognized Selector
 crash原因:找不到方法iOS系統(tǒng)拋出異常崩潰
 解決方案:
 1、方法調用前進行 respondsToSelector 判斷
 2、在.h中聲明的方法如果用不到就去掉,用得到就同時在.m文件中實現(xiàn)
 3、給NSObject添加一個分類,實現(xiàn)消息轉發(fā)的幾個方法
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)unrecognizedSelectorCrash {
    [self unrecognizedSelectorCrash1];
    [self unrecognizedSelectorCrash2];
    [self unrecognizedSelectorCrash3];
    [self unrecognizedSelectorCrash4];
    [self unrecognizedSelectorCrash5];
}
//1、未實現(xiàn).h聲明的方法
- (void)unrecognizedSelectorCrash1 {
    UnrecognizedSelectorVCObj* obj = [[UnrecognizedSelectorVCObj alloc] init];
    [obj onlyDefinMethod]; //crash崩潰:unrecognized selector sent to instance
}
//2、未實現(xiàn)遵守協(xié)議中的方法
- (void)unrecognizedSelectorCrash2 {
    UnrecognizedSelectorVCObj* obj = [[UnrecognizedSelectorVCObj alloc] init];
    [obj requiredProtocolMethodA];//crash崩潰:unrecognized selector sent to instance
}
//3、未實現(xiàn)代理方法
- (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、低版本系統(tǒng)使用高版本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原因: 觀察者沒有實現(xiàn)observeValueForKeyPath:ofObject:changecontext:方法:,會崩潰或多次移除觀察者
解決方案:觀察者需要實現(xiàn)observeValueForKeyPath方法,addObserver和removeObserver一定要成對出現(xiàn)

/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 場景三:KVC、KVO造成的crash
 KVC造成crash原因:給不存在的key(包括key為nil)設置value
 解決方案:重寫類的setValue:forUndefinedKey:和valueForUndefinedKey:
 
 KVO造成crash原因: 觀察者沒有實現(xiàn)observeValueForKeyPath:ofObject:changecontext:方法:,會崩潰或多次移除觀察者
 解決方案:觀察者需要實現(xiàn)observeValueForKeyPath方法
 addObserver和removeObserver一定要成對出現(xiàn),
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (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、沒有有實現(xiàn)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<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"object = %@, keyPath = %@", object,keyPath);
}

場景四:EXC_BAD_ACCESS

crash原因:出現(xiàn)懸掛指針,對象沒有被初始化,或者訪問的對象被釋放
解決方案:調用block的時候,做判斷; 對象的屬性使用正確的修飾方式(strong/weak/assign)

/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 場景四:EXC_BAD_ACCESS
 crash原因:出現(xiàn)懸掛指針,對象沒有被初始化,或者訪問的對象被釋放
 解決方案:
 調用block的時候,做判斷
 對象的屬性使用正確的修飾方式(strong/weak)
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)exc_bad_access_Crash {
    [self exc_bad_access_Crash1];
    [self exc_bad_access_Crash2];
    [self exc_bad_access_Crash3];
}
//1、訪問沒有實現(xiàn)的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];
    // 野指針場景三:給類添加添加關聯(lián)變量的時候,類似場景二,應該使用OBJC_ASSOCIATION_RETAIN_NONATOMIC修飾,卻錯誤使用OBJC_ASSOCIATION_ASSIGN
    [self.view addSubview:self.associateView];
}

場景五:多線程中的崩潰

crash原因:死鎖、子線程中更新UI、多個線程同時釋放一個對象
解決方案: 多線程遇到需要同步的時候,加鎖,添加信號量等進行同步操作。一般多線程發(fā)生的Crash,會收到SIGSEGV信號,表明試圖訪問未分配給自己的內存, 或試圖往沒有寫權限的內存地址寫數據。

/**
 --------------------------------------------------------------------------------------------------------------------------------------------
 場景五:多線程中的崩潰
 crash原因:死鎖、子線程中更新UI、多個線程同時釋放一個對象
 解決方案:
 多線程遇到需要同步的時候,加鎖,添加信號量等進行同步操作。一般多線程發(fā)生的Crash,會收到SIGSEGV信號,
 表明試圖訪問未分配給自己的內存, 或試圖往沒有寫權限的內存地址寫數據。
 --------------------------------------------------------------------------------------------------------------------------------------------
 */
- (void)GCDCrashCase {
    [self GCDCrashCase1];
    [self GCDCrashCase2];
    [self GCDCrashCase3];
    [self GCDCrashCase4];
}
//1、 dispatch_group_leave比dispatch_group_enter執(zhí)行的次數多
- (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), 然后繼續(xù)往后走到其他邏輯.而這時候, 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);
            }
        }
    });
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容