iOS中常見Crash總結

在路上重名了啊發表於2019-02-11

@(iOS總結)[溫故而知新] [TOC]

  • 1、找不到方法的實現unrecognized selector sent to instance
  • 2、KVC造成的crash
  • 3、EXC_BAD_ACCESS
  • 4、KVO引起的崩潰
  • 5、集合類相關崩潰
  • 6、多執行緒中的崩潰
  • 7、Socket長連線,進入後臺沒有關閉
  • 8、Watch Dog超時造成的crash
  • 9、後臺返回NSNull導致的崩潰,多見於Java做後臺伺服器開發語言

1、找不到方法的實現unrecognized selector sent to instance

1.1、場景對應的Code

#import "UnrecognizedSelectorVC.h"
/**
 代理協議
 */
@protocol UnrecognizedSelectorVCDelegate <NSObject>
@optional
- (void)notImplementionFunc;
@end
/**
 測試控制器的代理物件
 */
@interface UnrecognizedSelectorVCObj : NSObject<UnrecognizedSelectorVCDelegate>
@property (nonatomic, strong) NSString *name;
@end
@implementation UnrecognizedSelectorVCObj
@end
/**
 測試控制器
 */
@interface UnrecognizedSelectorVC ()
@property(nonatomic, weak) id<UnrecognizedSelectorVCDelegate> delegate;
@property(nonatomic, copy) NSMutableArray *mutableArray;
@end
@implementation UnrecognizedSelectorVC
- (void)viewDidLoad {
    [super viewDidLoad];
    [self case1];
}
/**
 場景一:沒有實現代理
 */
- (void)case1 {
    UnrecognizedSelectorVCObj* obj = [[UnrecognizedSelectorVCObj alloc] init];
    self.delegate = obj;
    // 崩潰:reason: '-[UnrecognizedSelectorVCObj notImplementionFunc]: unrecognized selector sent to instance 0x2808047f0'
    [self.delegate notImplementionFunc];
    // 解決辦法:應該使用下面的程式碼
    if ( [self.delegate respondsToSelector:@selector(notImplementionFunc)] ) {
        [self.delegate notImplementionFunc];
    }
}
/**
 場景二:可變屬性使用copy修飾
 */
- (void)case2 {
    NSMutableArray* array = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
    self.mutableArray = array;
    // 崩潰:reason: '-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x281198a50'
    [self.mutableArray addObject:@4];
    // 原因:NSMutableArray經過copy之後變成NSArray
	// @property (nonatomic, copy) NSMutableArray *mArray;
	// 等同於
	// - (void)setMArray:(NSMutableArray *)mArray {
	//    _mArray = mArray.copy;
	//}
    // 解決辦法:使用strong修飾或者重寫set方法

	// 知識點:集合類物件和非集合類物件的copy與mutableCopy
	// [NSArray copy]                  // 淺複製(新的和原來的是一個array)
	// [NSArray mutableCopy]           // 深複製(array是新的,但是內容還是原來的,內容的指標沒有變化)
	// [NSMutableArray copy]           // 深複製(array是新的,但是內容還是原來的,內容的指標沒有變化)
	// [NSMutableArray mutableCopy]    // 深複製(array是新的,但是內容還是原來的,內容的指標沒有變化)
}
/**
 場景三:低版本系統使用高版本API
 */
- (void)case3 {
    if (@available(iOS 10.0, *)) {
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            
        }];
    } else {
        // Fallback on earlier versions
    }
}
@end
複製程式碼

1.2、原因

找不到方法iOS系統丟擲異常崩潰

1.3、解決方案:

  • 1、給NSObject新增一個分類,實現訊息轉發的幾個方法
#import "NSObject+SelectorCrash.h"
@implementation NSObject (SelectorCrash)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([self respondsToSelector:aSelector]) {
        // 已實現不做處理
        return [self methodSignatureForSelector:aSelector];
    }
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"在 %@ 類中, 呼叫了沒有實現的例項方法: %@ ",NSStringFromClass([self class]),NSStringFromSelector(anInvocation.selector));
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([self respondsToSelector:aSelector]) {
        // 已實現不做處理
        return [self methodSignatureForSelector:aSelector];
    }
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"在 %@ 類中, 呼叫了沒有實現的類方法: %@ ",NSStringFromClass([self class]),NSStringFromSelector(anInvocation.selector));
}
複製程式碼
  • 2、儘量避免使用performSelector一系列方法
  • 3、delegate 方法呼叫前進行 respondsToSelector 判斷,或者Release模式下使用ProtocolKit給協議新增預設實現防止崩潰,Debug模式下關閉預設實現
  • 4、屬性成員變數不要重名定義,合理使用 synthesize 生成屬性的 settergetter 方法
  • 5、在MRC模式下,變數的 retainrelease 要謹慎,建議採用安全 release 方法,即 release 的物件置為 nil
  • 6、在.h中宣告的方法如果用不到就去掉,用得到就同時在.m檔案中實現
  • 7、可變屬性(如NSMutableArray),不要使用copy修飾,或者重寫set方法
  • 8、使用高版本的系統方法的時候做判斷

1.4、知識歸納:參考runtime 訊息轉發

訊息轉發機制主要包含三個步驟

  • 1、動態方法解析階段

    +(BOOL)resolveClassMethod:(SEL)sel或者+(BOOL)resolveInstanceMethod:(SEL)sel

  • 2、備用接收者階段

    - (id)forwardingTargetForSelector:(SEL)aSelector

  • 3、完整訊息轉發階段

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

    - (void)forwardInvocation:(NSInvocation *)anInvocation


圖片摘自:Objective-C 訊息傳送與轉發機制原理

iOS中常見Crash總結

使用命令:thread backtrace檢視執行緒堆疊

2、KVC造成的crash

2.1、場景對應的Code

#import "KvcCrashVC.h"

@interface KvcCrashVCObj : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation KvcCrashVCObj
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {   
}
- (id)valueForUndefinedKey:(NSString *)key {
    return nil;
}
@end

@interface KvcCrashVC ()
@end
@implementation KvcCrashVC
- (void)viewDidLoad {
    [super viewDidLoad];
    [self case1];
}
/**
 場景一:物件不支援KVC
 */
- (void)case1 {
    // reason: '[<NSObject 0x282fe7f90> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key key.'
    NSObject* obj = [[NSObject alloc]init];
    [obj setValue:@"value" forKey:@"key"];
}
/**
 場景二:key為nil
 */
- (void)case2 {
    // reason: '*** -[KvcCrashVCObj setValue:forKey:]: attempt to set a value for a nil key'
    KvcCrashVCObj* obj = [[KvcCrashVCObj alloc]init];
    // value 為nil不會崩潰
    [obj setValue:nil forKey:@"name"];
    // key為nil會崩潰(直接寫nil編譯器會提示警告,更多時候我們傳的是變數)
    [obj setValue:@"value" forKey:nil];
}
/**
 場景三:key不是object的屬性產生的crash
 */
- (void)case3 {
    // reason: '[<KvcCrashVCObj 0x2810bfa80> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key falseKey.'
    KvcCrashVCObj* obj = [[KvcCrashVCObj alloc]init];
    [obj setValue:nil forKey:@"falseKey"];
}
@end
複製程式碼

2.2、原因

給不存在的key(包括key為nil)設定value

  • [obj setValue:@"value" forKey:@"UndefinedKey"]
  • [obj valueForKey:@"UndefinedKey"]

2.3、場景:

2.4、解決方案:

  • 1、如果屬性存在,利用iOS的反射機制來規避,NSStringFromSelector(@selector())SEL反射為字串作為key。這樣在@selector()中傳入方法名的過程中,編譯器會有合法性檢查,如果方法不存在或未實現會報黃色警告。
  • 2、重寫類的setValue:forUndefinedKey:valueForUndefinedKey:
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{

}
-(id)valueForUndefinedKey:(NSString *)key{
    return nil;
}
複製程式碼

2.5、知識歸納:參考runtime kvc原理分析

3、EXC_BAD_ACCESS

經過ARC的洗禮之後,普通的訪問釋放物件產生的EXC_BAD_ACCESS已經大量減少了,現在出現的EXC_BAD_ACCESS有很大一部分來自malloc的物件或者越界訪問。

3.1、場景對應的Code

#import "BadAccessCrashVC.h"
#import <objc/runtime.h>
@interface BadAccessCrashVC (AssociatedObject)
@property (nonatomic, strong) UIView *associateView;
@end
@implementation BadAccessCrashVC (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 BadAccessCrashVC ()
@property (nonatomic, copy)                         void(^blcok)(void);
@property (nonatomic, weak) UIView*                 weakView;
@property (nonatomic, unsafe_unretained) UIView*    unSafeView;
@property (nonatomic, assign) UIView*               assignView;
@end
@implementation BadAccessCrashVC
- (void)viewDidLoad {
    [super viewDidLoad];
    [self case1];
}
/**
 懸掛指標:訪問沒有實現的blcok
 */
- (void)case1 {
    self.blcok();
}
/**
 懸掛指標:物件沒有被初始化
 */
- (void)case2 {
    UIView* view = [UIView alloc];
    view.backgroundColor = [UIColor blackColor];
    [self.view addSubview:view];
}
/**
 懸掛指標:訪問的物件已經被釋放掉
 */
- (void)case3 {
    {
        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];
}
@end
複製程式碼

3.2、原因

出現懸掛指標,物件沒有被初始化,或者訪問的物件被釋放

3.3、解決方案:

  • 1、Debug階段開啟殭屍模式,Release時關閉殭屍模式
  • 2、使用Xcode的Address Sanitizer檢查地址訪問越界
  • 3、建立物件的時候記得初始化
  • 4、物件的屬性使用正確的修飾方式(strong/weak
  • 5、呼叫block的時候,做判斷

4、KVO引起的崩潰

4.1、場景對應的Code

#import "KvoCrashVC.h"

@interface KvoCrashVCObj : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation KvoCrashVCObj
@end

@interface KvoCrashVC ()
@property (nonatomic, strong) KvoCrashVCObj *sObj;
@end
@implementation KvoCrashVC
- (void)viewDidLoad {
    [super viewDidLoad];
    self.sObj = [[KvoCrashVCObj alloc] init];
//#import <XXShield/XXShield.h>
//    static dispatch_once_t onceToken;
//    dispatch_once(&onceToken, ^{
//        [XXShieldSDK registerStabilityWithAbility:(EXXShieldTypeKVO)];
//    });
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self func4];
}
/**
 觀察者是區域性變數,會崩潰
 */
- (void)func1 {
    // 崩潰日誌:An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
    KvoCrashVCObj* obj = [[KvoCrashVCObj alloc] init];
    [self addObserver:obj
           forKeyPath:@"view"
              options:NSKeyValueObservingOptionNew
              context:nil];
    self.view = [[UIView alloc] init];
}
/**
 被觀察者是區域性變數,會崩潰
 */
- (void)func2 {
    // 崩潰日誌:An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
    KvoCrashVCObj* obj = [[KvoCrashVCObj alloc] init];
    [obj addObserver:self
          forKeyPath:@"name"
             options:NSKeyValueObservingOptionNew
             context:nil];
    obj.name = @"";
}
/**
 沒有實現observeValueForKeyPath:ofObject:changecontext:方法:,會崩潰
 */
- (void)func3 {
    // 崩潰日誌:An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
    [self.sObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
    self.sObj.name = @"0";
}
/**
 重複移除觀察者,會崩潰
 */
- (void)func4 {
    // 崩潰日誌:because it is not registered as an observer
    [self.sObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
    self.sObj.name = @"0";
    [self.sObj removeObserver:self forKeyPath:@"name"];
    [self.sObj removeObserver:self forKeyPath:@"name"];
}
/**
 重複新增觀察者,不會崩潰,但是新增多少次,一次改變就會被觀察多少次
 */
- (void)func5 {
    [self.sObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
    self.sObj.name = @"0";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"keyPath = %@", keyPath);
}
// 總結:KVO有兩種崩潰
// 1、because it is not registered as an observer
// 2、An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
@end
複製程式碼

4.2、原因

新增了觀察者,沒有在正確的時機移除

4.3、解決方案:

  • 1、addObserver和removeObserver一定要成對出現,
  • 2、推薦使用FaceBook開源的第三方庫 FBKVOController

5、集合類相關崩潰

5.1、場景對應的Code

#import "CollectionCrashVC.h"

@interface CollectionCrashVC ()
@end
@implementation CollectionCrashVC
- (void)viewDidLoad {
    [super viewDidLoad];
    [self case4];
}
/**
 場景一:陣列越界
 */
- (void)case1 {
    // reason: '*** -[__NSArrayI objectAtIndex:]: index 4 beyond bounds [0 .. 2]'
    NSArray* array = [[NSArray alloc]initWithObjects:@1, @2, @3, nil];
    NSNumber* number = [array objectAtIndex:4];
    NSLog(@"number = %@", number);
}
/**
 場景二:向陣列中新增nil元素
 */
- (void)case2 {
    // reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
    NSMutableArray* array = [[NSMutableArray alloc]initWithObjects:@1, @2, @3, nil];
    [array addObject:nil];
}
/**
 場景三:陣列遍歷的時候使用錯誤的方式移除元素
 */
- (void)case3 {
    NSMutableArray<NSNumber*>* array = [NSMutableArray array];
    [array addObject:@1];
    [array addObject:@2];
    [array addObject:@3];
    [array addObject:@4];
    // 不崩潰
    [array enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (obj.integerValue == 1) {
            [array removeObject:obj];
        }
    }];
    // 崩潰,reason: '*** Collection <__NSArrayM: 0x2829946f0> was mutated while being enumerated.'
    for (NSNumber* obj in array) {
        if (obj.integerValue == 2) {
            [array removeObject:obj];
        }
    }
    //    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    //        self.view.backgroundColor = [UIColor blueColor];
    //    });
}
/**
 場景四:使用setObject:forKey:向字典中新增value為nil的鍵值對,推薦使用KVC的setValue:nil forKey:
 */
- (void)case4 {
    NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
    [dictionary setObject:@1 forKey:@1];
    // 不崩潰:value為nil,只會移除key對應的鍵值對
    [dictionary setValue:nil forKey:@1];
    // 崩潰:reason: '*** -[__NSDictionaryM setObject:forKey:]: object cannot be nil (key: 1)'
    [dictionary setObject:nil forKey:@1];
}
@end
複製程式碼

5.2、原因

越界、新增nil、多執行緒非原子性操作、遍歷的同時移除元素

5.3、場景:

  • 1、陣列越界,訪問下標大於陣列的個數
  • 2、向陣列中新增空資料
  • 3、多執行緒環境中,一個執行緒在讀取,一個執行緒在移除
  • 4、一邊遍歷陣列,一邊移除陣列中的元素
  • 5、多執行緒中操作可變陣列(陣列的擴容、訪問殭屍物件)

5.4、解決方案:

  • 1、給集合類新增category重寫原來的方法,在內部做判斷
  • 2、使用Runtime把原來的方法替換成自定義的安全方法
  • 3、給NSMutableDictionary新增元素的時候,使用setObject:forKey:向字典中新增value為nil的鍵值對,推薦使用KVC的setValue:nil forKey:[mutableDictionary setValue:nil ForKey:@"name"]不會崩潰,只是從字典中移除name鍵值對。
  • 4、因為NSMutableArray、NSMutableDictionary不是執行緒安全的,所以在多執行緒環境下要保證讀寫操作的原子性,使用 加鎖訊號量GCD序列佇列GCD柵欄dispatch_barrier_asyncCGD組dispatch_group_enterdispatch_group_leave

6、多執行緒中的崩潰

6.1、場景對應的Code

#import "ThreadCrashVC.h"

@interface ThreadCrashVC ()
@property (nonatomic, strong) NSMutableArray *array;
@end

@implementation ThreadCrashVC
- (void)viewDidLoad {
    [super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self case1];
}
/**
 dispatch_group_leave比dispatch_group_enter執行的次數多
 */
- (void)case1 {
    // 崩潰:Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1054f6348)
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_leave(group);
}
/**
 在子執行緒更新UI
 */
- (void)case2 {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        self.view.backgroundColor = [UIColor redColor];
    });
}
/**
 多個執行緒同時釋放一個物件
 */
- (void)case3 {   
    // ==================使用訊號量同步後不崩潰==================
    {
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
        __block NSObject *obj = [NSObject new];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            while (YES) {
                dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
                obj = [NSObject new];
                dispatch_semaphore_signal(semaphore);
            }
        });
        while (YES) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            obj = [NSObject new];
            dispatch_semaphore_signal(semaphore);
        }
    }
    // ==================未同步則崩潰==================
    {
        __block NSObject *obj = [NSObject new];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            while (YES) {
                obj = [NSObject new];
            }
        });
        while (YES) {
            obj = [NSObject new];
        }
    }
}
/**
 多執行緒中的陣列擴容、淺複製
 擴容:陣列的地址已經改變,報錯was mutated while being enumerated
 淺複製:訪問殭屍物件,報錯EXC_BAD_ACCESS
 
 // 知識點:集合類物件和非集合類物件的copy與mutableCopy
 // [NSArray copy]                  // 淺複製
 // [NSArray mutableCopy]           // 深複製
 // [NSMutableArray copy]           // 深複製
 // [NSMutableArray mutableCopy]    // 深複製
 
 參考:
 [Swift陣列擴容原理](https://bestswifter.com/swiftarrayappend/)
 [戴倉薯](https://juejin.im/post/5a9aa633518825556a71d9f3)
 */
-(void)case4 {
    {
        NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];
        NSArray *copyArray = [array copy];
        NSMutableArray *mCopyArray = [array mutableCopy];
        NSLog(@"array = %p,copyArray = %p,mCopyArray = %p", array, copyArray, mCopyArray);
    }
    {
        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);
    }
    
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", 0);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", 0);
    
    NSMutableArray* array = [NSMutableArray array];
    
    dispatch_async(queue1, ^{
        while (true) {
            if (array.count < 10) {
                [array addObject:@(array.count)];
            } else {
                [array removeAllObjects];
            }
        }
    });
    
    dispatch_async(queue2, ^{
        while (true) {
            // case 1:陣列擴容
            for (NSNumber* number in array) {
              NSLog(@"%@", number);
            }
            // case 2:陣列擴容
            NSArray* immutableArray = array;
            for (NSNumber* number in immutableArray) {
              NSLog(@"%@", number);
            }
            // case 3:淺複製 在 [NSArray copy] 的過程,
            // copy 方法內部呼叫initWithArray:range:copyItems: 時
            // 陣列被另一個執行緒清空,range 不一致導致丟擲 exception
            NSArray* immutableArray1 = [array copy];
            for (NSNumber* number in immutableArray1) {
                NSLog(@"%@", number);
            }
            // case 4:淺複製 陣列內的物件被其他執行緒釋放,訪問殭屍物件
            NSArray* immutableArray2 = [array mutableCopy];
            for (NSNumber* number in immutableArray2) {
                NSLog(@"%@", number);
            }
        }
    });
}
@end
複製程式碼

6.2、原因

死鎖、子執行緒中更新UI、多個執行緒同時釋放一個物件

6.3、場景

  • 1、在子執行緒中更新UI
  • 2、dispatch_group crash,dispatch_group_leave的次數比dispatch_group_enter次數多。參考:iOS疑難問題排查之深入探究dispatch_group crash
  • 3、多執行緒下非執行緒安全類的使用,如NSMutableArrayNSMutableDictionary。NSCache是執行緒安全的。
  • 4、資料快取到磁碟和讀取。

6.4、解決方案:

多執行緒遇到需要同步的時候,加鎖,新增訊號量等進行同步操作。一般多執行緒發生的Crash,會收到SIGSEGV訊號,表明試圖訪問未分配給自己的記憶體, 或試圖往沒有寫許可權的記憶體地址寫資料。

7、Socket長連線,進入後臺沒有關閉

當伺服器close一個連線時,若client端接著發資料。根據TCP協議的規定,會收到一個RST響應,client再往這個伺服器傳送資料時,系統會發出一個SIGPIPE訊號給程式,告訴程式這個連線已經斷開了,不要再寫了。而根據訊號的預設處理規則,SIGPIPE訊號的預設執行動作是terminate(終止、退出),所以client會退出。

長連線socket或重定向管道進入後臺,沒有關閉導致崩潰的解決辦法:

7.1、解決方案:

  • 方法一:1、切換到後臺是,關閉長連線和管道,回到前臺重新建立。
  • 方法二:2、使用signal(SIGPIPE,SIG_IGN),將SIGPIP交給系統處理,這麼做將SIGPIPE設為SIG_IGN,使客戶端不執行預設操作,即不退出。

8、Watch Dog超時造成的crash

主執行緒執行耗時操作,導致主執行緒被卡超過一定時間。一般異常編碼是0x8badf00d,表示應用發生watch dog超時而被iOS終止,通常是應用花費太多的時間無法啟動、終止或者響應系統事件。

8.1、解決方案:

主執行緒只負責更新UI和事件響應,將耗時操作(網路請求、資料庫讀寫等)非同步放到後臺執行緒執行。

9、後臺返回NSNull導致的崩潰,多見於Java做後臺伺服器開發語言

9.1、場景對應的Code

// reason: '-[NSNull integerValue]: unrecognized selector sent to instance 0x2098f99b0'
NSNull *nullStr = [[NSNull alloc] init];
NSMutableDictionary* dic = [NSMutableDictionary dictionary];
[dic setValue:nullStr forKey:@"key"];
NSNumber* number = [dic valueForKey:@"key"];
複製程式碼
  • NULL:用於普通型別,例如NSInteger
  • nil:用於OC物件(除了類這個物件),給nil物件傳送訊息不會crash
  • Nil:用於Class型別物件的賦值(類是元類的例項,也是物件)
  • NSNull:用於OC物件的站位,一般會作為集合中的佔位元素,給NSNull物件傳送訊息會crash的,後臺給我們返回的就是NSNull物件

9.2、解決方法

利用訊息轉發。參考:NullSafe。當我們給一個NSNull物件傳送訊息的話,可能會崩潰(null是有記憶體的),而傳送給nil的話,是不會崩潰的。

10、在iOS中捕獲異常資訊

崩潰主要是由於 Mach 異常、Objective-C 異常(NSException)引起的,同時對於 Mach 異常,到了 BSD 層會轉換為對應的 Signal 訊號,那麼我們也可以通過捕獲訊號,來捕獲 Crash 事件。針對 NSException 可以通過註冊 NSUncaughtExceptionHandler 捕獲異常資訊。

/**
 一、OC異常處理函式

 @param exception exception
 */
static void uncaught_exception_handler (NSException *exception) {
    // 異常的堆疊資訊
    NSArray *stackArray = [exception callStackSymbols];
    // 出現異常的原因
    NSString *reason = [exception reason];
    // 異常名稱
    NSString *name = [exception name];
    NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",
                               name,
                               reason,
                               stackArray];
    NSLog(@"%@", exceptionInfo);
    NSMutableArray *tmpArr = [NSMutableArray arrayWithArray:stackArray];
    [tmpArr insertObject:reason atIndex:0];
    //儲存到本地  --  當然你可以在下次啟動的時候,上傳這個log
    [exceptionInfo writeToFile:[NSString stringWithFormat:@"%@/Documents/error.log",NSHomeDirectory()]
                    atomically:YES
                      encoding:NSUTF8StringEncoding
                         error:nil];
    
    /**
     *  把異常崩潰資訊傳送至開發者郵件
     */
    NSString *content = [NSString stringWithFormat:@"==異常錯誤報告==\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@",
                         name,
                         reason,
                         [stackArray componentsJoinedByString:@"\n"]];
    NSMutableString *mailUrl = [NSMutableString string];
    [mailUrl appendString:@"mailto:test@qq.com"];
    [mailUrl appendString:@"?subject=程式異常崩潰,請配合傳送異常報告,謝謝合作!"];
    [mailUrl appendFormat:@"&body=%@", content];
    // 開啟地址
    NSCharacterSet *set = [NSCharacterSet URLHostAllowedCharacterSet];
    NSString *mailPath = [mailUrl stringByAddingPercentEncodingWithAllowedCharacters:set];
    //NSString *mailPath = [mailUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailPath]];
}
/**
 二、Unix標準的signal機制處理函式

 @param sig sig
 */
static void handleSignal( int sig ) {
    
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // OC層中未被捕獲的異常,通過註冊NSUncaughtExceptionHandler捕獲異常資訊
    NSSetUncaughtExceptionHandler(&uncaught_exception_handler);
    // 記憶體訪問錯誤,重複釋放等錯誤就無能為力了,因為這種錯誤它丟擲的是Signal,所以必須要專門做Signal處理
    // OC中層不能轉換的Mach異常,利用Unix標準的signal機制,註冊SIGABRT, SIGBUS, SIGSEGV等訊號發生時的處理函式。
    signal(SIGSEGV,handleSignal);
    return YES;
}
複製程式碼

參考資料

大白健康系統--iOS APP執行時Crash自動修復系統

iOS中的crash防護(一)unrecognized selector sent to instance

小蘿莉說Crash(一):Unrecognized selector sent to instance xxxx

iOS實錄14:淺談iOS Crash(一)

iOS崩潰異常全域性捕獲

聊聊dealloc

iOS崩潰crash大解析

GitHub開源實現:

相關文章