iOS - 常見Crash

常見的Crash

  1. 找不到方法的實現unrecognized selector sent to instance
  2. KVC形成的Crash
  3. EXC_BAD_ACCESS
  4. KVO引發的崩潰
  5. 集合類相關崩潰
  6. 多線程中崩潰
  7. Socket長鏈接,進入後臺沒有關閉
  8. Watch Dog形成的崩潰
  9. 後臺返回NSNull形成的崩潰

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

1.1 對應代碼

person.hgit

@protocol PersonDelegate <NSObject>

- (void)didChangedName;

@end

@interface Person : NSObject

//該方法在.m未實現
- (void)eatFood;

@property (nonatomic, weak) id<PersonDelegate>delegate;

@end
複製代碼

ViewController.mgithub

#import "ViewController.h"
#import "Person.h"

@interface ViewController ()

@property (nonatomic, strong) NSMutableArray *mutableArray1;
@property (nonatomic, copy) NSMutableArray *mutableArray2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self case4];
}

- (void)case1 {
    /*
     crash1. eatFood 聲明未實現
     解決方案:.m實現eatFood方法,或消息轉發
     */
    Person *person = [[Person alloc] init];
    person.delegate = self;
    [person eatFood];
}

- (void)case2 {
    /*
     crash2. 代理未實現
     解決方案:
     if (person.delegate && [person.delegate respondsToSelector:@selector(didChangedName)]) {
         [person.delegate didChangedName];
     }
     */
    Person *person = [[Person alloc] init];
    person.delegate = self;
    [person.delegate didChangedName];
}

- (void)case3 {
    NSMutableArray *array = [NSMutableArray arrayWithObjects:@"1",@"2",@"3", nil];
    self.mutableArray1 = array; //strong修飾的 mutableArray1,此處是NSMutableArray,與array指針地址一致
    self.mutableArray2 = array; //copy修飾的mutableArray2,此處是NSArray,是新對象,與array指針地址不一致
    [self.mutableArray1 addObject:@"4"];
    //下邊這行代碼致使: -[__NSArrayI addObject:]: unrecognized selector sent to instance 0x600002628ea0
    //緣由:copy修飾的NSMutableArray,在51行會將array淺拷貝,self.mutableArray2變成NSArray, [NSArray addObject] crash
    //解決方案:用strong修飾或重寫setter方法
    [self.mutableArray2 addObject:@"4"];
    
    //附:
    //[NSArray copy] 淺拷貝,生成NSArray
    //[NSArray mutableCopy] 深拷貝, 生成NSMutableArray
    //[NSMutableArray copy] 深拷貝,生成是NSArray
    //[NSMutableArray mutableCopy] 深拷貝,生成NSMutableArray
}

- (void)case4 {
    //低版本調用高版本API
    if (@available(iOS 10.0, *)) {
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:true block:^(NSTimer * _Nonnull timer) {
        }];
    } else {
        // Fallback on earlier versions
    }
}
複製代碼

1.3 緣由

找不到執行的方法(附:給nil對象發消息不會crash,但給非nil對象發未實現的消息會crash)數據庫

1.4 解決方案總結

  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(@"在 %@ 類中, 調用了沒有實現的 %@ 實例方法", [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(@"在 %@ 類中, 調用了沒有實現的 %@ 類方法", [self class], NSStringFromSelector(anInvocation.selector));
}

@end
複製代碼
  1. delegate 方法調用前進行delegate,respondsToSelector判斷
  2. .h聲明的方法添加實現
  3. NSMutableArray儘可能使用strong修飾(同時注意數據修改問題)
  4. 使用系統API時進行版本判斷

1.5 Runtime消息動態解析、轉發

消息動態解析

整個消息發生流程

2. KVC形成的crash

2.1 對應代碼

@interface KVCCrashVCObj : NSObject

@property (nonatomic, copy) NSString *nickname;

@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.view.backgroundColor = [UIColor lightGrayColor];
    
    //[self case1];
    [self case2];
}

- (void)case1 {
    /*
     對象不支持kvc
     reason: '[<KVCCrashVCObj 0x6000008ccd30> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key key.'
        
     */
    KVCCrashVCObj *obj = [[KVCCrashVCObj alloc] init];
    [obj setValue:@"value" forKey:@"key"];
}

- (void)case2 {
    /*
     key設置爲nil
     reason: '*** -[KVCCrashVCObj setValue:forKey:]: attempt to set a value for a nil key'
     */
    KVCCrashVCObj *obj = [[KVCCrashVCObj alloc] init];
    [obj setValue:nil forKey:@"nickname"];
    [obj setValue:@"value" forKey:nil];
}

- (void)case3 {
    /*
     經過不存在的key取值
     reason: '[<KVCCrashVCObj 0x6000019f3150> valueForUndefinedKey:]: this class is not key value coding-compliant for the key key.'
     解決方案:
     KVCCrashVCObj 重寫 - (id)valueForUndefinedKey:(NSString *)key
     */
    KVCCrashVCObj *obj = [[KVCCrashVCObj alloc] init];
    NSString *nickname = [obj valueForKey:@"key"];
}

@end

複製代碼

2.2 緣由

給不存在的key或nil設置value值, 經過不存在的key取值數組

[obj setValue: @"value" forKey: @"undefinedKey"];
[obj setValue: @"value" forKey: nil];
[obj valueForKey: @"undifinedKey"];

複製代碼

2.3 解決方案

  1. 若是屬性存在,經過iOS的反射機制來規避,[obj setValue:@"value" forKey:NSStringFromSelector(@selector(undifinedKey))];, 將SEL反射爲字符串作爲key,這樣在@selecor()中傳入方法名的時候,編譯器會作檢查,若是方法不存在會報警告
  2. 重寫類的setValue:forUndefinedKey:valueForUndefinedKey:

3. KVO致使的crash

3.1 KVO知識回顧

3.1.1 KVO參數說明

KVOCrashObj* obj = [[KVOCrashObj alloc] init];
    /*
     1. 觀察者:obj
     2. 被觀察者:self
     3. 觀察的對象:view
     4. context:可選的參數,會隨着觀察消息傳遞,
     用於區分接收該消息的觀察者。通常狀況下,只需經過 keyPath 就能夠判斷接收消息的觀察者。可是當父類子類都觀察了同一個 keyPath 時,僅靠 keyPath 就沒法判斷消息該傳給子類,仍是傳給父類。
     5. 須要在觀察者類KVOCrashObj裏實現 observeValueForKeyPath: ofObject: change: context:,才能接受到被觀察者self的view變化
     
     */
    [self addObserver:obj forKeyPath:@"view" options:NSKeyValueObservingOptionNew context:nil];
複製代碼

3.1.2 KVO本質

iOS用什麼方式實現對一個對象的KVO?(KVO的本質是什麼?)安全

答. 當一個對象使用了KVO監聽,iOS系統會修改這個對象的isa指針,改成指向一個全新的經過Runtime動態建立的子類,子類擁有本身的set方法實現,set方法實現內部會順序調用willChangeValueForKey方法、原來的setter方法實現、didChangeValueForKey方法,而didChangeValueForKey方法內部又會調用監聽器的observeValueForKeyPath:ofObject:change:context:監聽方法。性能優化

3.1 對應代碼

#import "KVOCrashVC.h"

@interface KVOCrashObj : NSObject
@property (nonatomic, copy) NSString *nickname;
@end

@implementation KVOCrashObj
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@ 觀察到了%@ 的 %@發生了改變",[self class],[object class],keyPath);
}
@end

@interface KVOCrashVC ()
@property (nonatomic, strong) KVOCrashObj *kvoObj;
@end

@implementation KVOCrashVC

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor lightTextColor];
    
    self.kvoObj = [[KVOCrashObj alloc] init];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self case2];
}


- (void)case1 {
    /*
     self觀察obj的nickname值改變,在self vc沒有實現observeValueForKeyPath:ofObject:changecontext:致使crash
     reason: '<KVOCrashVC: 0x7f84dc617a20>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
     Key path: nickname
     Observed object: <KVOCrashObj: 0x60000268a120>
     Change: {
         kind = 1;
         new = "";
     }
     Context: 0x0'
     */
    
    KVOCrashObj* obj = [[KVOCrashObj alloc] init];
    [self addObserver:obj forKeyPath:@"view" options:NSKeyValueObservingOptionNew context:nil];
    [obj addObserver:self
          forKeyPath:@"nickname"
             options:NSKeyValueObservingOptionNew
             context:nil];
    obj.nickname = @"";
}

- (void)case2 {
    /* 重複移除觀察者致使崩潰
     reason: 'Cannot remove an observer <KVOCrashVC 0x7f8199912f00> for the key path "nickname" from <KVOCrashObj 0x6000004f5780> because it is not registered as an observer.'
     */
    [self.kvoObj addObserver:self forKeyPath:@"nickname" options:NSKeyValueObservingOptionNew context:nil];
    self.kvoObj.nickname = @"objc.c";
    [self.kvoObj removeObserver:self forKeyPath:@"nickname"];
    [self.kvoObj removeObserver:self forKeyPath:@"nickname"];
}
    

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@ 觀察到了%@ 的 %@發生了改變",[self class],[object class],keyPath);
}

@end

複製代碼

3.2 緣由

  1. 添加觀察者後未實現observeValueForKeyPath: ofObject: change: context:方法
  2. 重複移除觀察者

3.3 解決方案

  1. 觀察者必須實現observeValueForKeyPath: ofObject: change: context:方法
  2. addobserverremoveObserver必須成對出現

4. EXC_BAD_ACCESS

4.1 對應代碼

#import "BadAccessCrashVC.h"
#import <objc/runtime.h>

@interface BadAccessCrashVC (AssociatedObject)
@property (nonatomic, strong) UIView *associatedView;
@end

@implementation BadAccessCrashVC (AssociatedObject)

- (void)setAssociatedView:(UIView *)associatedView {
    /*
     self: 關聯對象的類
     key:  要保證全局惟一,key與關聯的對象是一一對應關係,必須全局惟一,一般用@selector(methodName)作爲key
     value: 要關聯的對象
     policy:關聯策略
     OBJC_ASSOCIATION_COPY: 至關於@property(atomic,copy)
     OBJC_ASSOCIATION_COPY_NONATOMIC: 至關於@property(nonatomic, copy)
     OBJC_ASSOCIATION_ASSIGN: 至關於@property(assign)
     OBJC_ASSOCIATION_RETAIN: 至關於@property(atomic, strong)
     OBJC_ASSOCIATION_RETAIN_NONATOMIC: 至關於@property(nonatomic, strong)
     */
    objc_setAssociatedObject(self, @selector(associatedView), associatedView, OBJC_ASSOCIATION_ASSIGN);
    //objc_setAssociatedObject(self, @selector(associatedView), associatedView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *)associatedView {
    return objc_getAssociatedObject(self, _cmd);
}
@end


@interface BadAccessCrashVC ()

@property (nonatomic, copy) void(^block)(void);
@property (nonatomic, weak) UIView *weakView;
@property (nonatomic, unsafe_unretained) UIView *unsafeView;
@property (nonatomic, assign) UIView *assignView;
@end

@implementation BadAccessCrashVC

- (void)viewDidLoad {
    self.view.backgroundColor = [UIColor orangeColor];
    [super viewDidLoad];
    
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self case3];
}

- (void)case1 {
    /*
     懸掛指針:訪問沒有實現的Block
     Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)
     */
    self.block();
}

- (void)case2 {
    /*
     懸掛指針:對象沒有被初始化
     Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
     */
    UIView *view = [UIView alloc];
    view.backgroundColor = [UIColor redColor];
    [self.view addSubview:view];
}

- (void)case3 {
    {
    UIView *view = [[UIView alloc]init];
    view.backgroundColor = [UIColor redColor];
    self.weakView = view;
    self.unsafeView = view;
    self.assignView = view;
    self.associatedView = view;
    }
    
    //addSubview:nil時不會crash
    //如下崩潰都是Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    //不會crash, arc下view釋放後,weakView會置爲nil,所以這行代碼不會崩潰
    [self.view addSubview:self.weakView];
    //野指針場景一: unsafeunreatin修飾的對象釋放後,不會自動置爲nil,變成野指針,所以崩潰
    [self.view addSubview:self.unsafeView];
    //野指針場景二:應該使用strong/weak修飾的對象,卻錯誤的使用了assign,釋放後不會置爲nil
    [self.view addSubview:self.assignView];
    //野指針場景三:給類添加關聯變量時,相似場景二,應該使用OBJC_ASSOCIATION_RETAIN,卻錯誤的使用了OBJC_ASSOCIATION_ASSIGN
    [self.view addSubview:self.associatedView];
}

@end

複製代碼

4.2 緣由

出現懸掛指針、訪問未被初始化對象、訪問野指針服務器

4.3 解決方案

  1. Debug環境開啓Zombie Objects,Release關閉
  2. 使用Xcode的Address Sanitizer檢查地址訪問越界
  3. 建立對象的時候記得初始化
  4. 對象的修飾符使用正確
  5. 調用Block的時候,作判斷

5 集合類崩潰

5.1 對應代碼

#import "CollectionCrashVC.h"

@interface CollectionCrashVC ()

@end

@implementation CollectionCrashVC

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor yellowColor];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self case3];
}

- (void)case1 {
    /*
    數組越界
     reason: '*** -[__NSArrayM objectAtIndex:]: index 3 beyond bounds [0 .. 2]
     */
    NSMutableArray *array = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
    NSNumber *number = [array objectAtIndex:3];
    NSLog(@"number: %@", number);
}

- (void)case2 {
    /*
     向集合中插入nil元素
     reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil
     */
    NSMutableArray *array = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
    [array addObject:nil];
}

- (void)case3 {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:@1 forKey:@"1"];
    //不會崩潰,value爲nil,只會移除對應的鍵值對
    [dict setValue:nil forKey:@"1"];
    //崩潰:reason: '*** -[__NSDictionaryM setObject:forKey:]: object cannot be nil (key: 1)'
    [dict setObject:nil forKey:@"1"];
}

@end
複製代碼

5.2 緣由

  1. 數組越界
  2. 向數組中添加nil元素
  3. 一邊遍歷數組,一邊移除數組中元素
  4. 多線程中操做數組:數組擴容、訪問殭屍對象等
  5. 字典setObject:nil forKey:@"key"

5.4 解決方案

  1. runtime swizzling交換集合取值方法,取值的時候作判斷
  2. NSMutableArray添加元素時,使用setValue:forKey:,這個方法向字典中添加nil時,不會崩潰,只會刪除對應鍵值對
  3. 由於NSMutableArray、NSMutableDictionary不是線程安全的,因此在多線程環境下要保證讀寫操做的原子性,能夠加鎖、信號量、GCD串行隊列、GCD柵欄

dispatch_barrier_asyncdispatch_groupmarkdown

6. 多線程崩潰

6.1 對應代碼

#import "ThreadCrashVC.h"

@interface ThreadCrashVC ()

@end

@implementation ThreadCrashVC

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self case4];
}

- (void)case1 {
    //dispatch_group_leave 比dispatch_group_enter多
    //Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_leave(group);
}

- (void)case2 {
    //子線程中刷新UI
    //ain Thread Checker: UI API called on a background thread: -[UIViewController view]
    dispatch_queue_t queue = dispatch_queue_create("com.objc.c", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue , ^{
        NSLog(@"thread: %@", [NSThread currentThread]);
        self.view.backgroundColor = [UIColor yellowColor];
    });
}

- (void)case3 {
    //使用信號量後不會崩潰
    {
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
        __block NSObject *obj = [[NSObject alloc] init];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            while (YES) {
                NSLog(@"dispatch_async -- 1");
                dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
                obj = [[NSObject alloc] init];
                dispatch_semaphore_signal(semaphore);
            }
        });
        
        while (YES) {
            NSLog(@"dispatch_sync -- 2");
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            obj = [[NSObject alloc] init];
            dispatch_semaphore_signal(semaphore);
        }

    }
    
    //不使用信號量,多線程同時釋放對象致使崩潰
    {
        __block NSObject *obj = [[NSObject alloc] init];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            while (YES) {
                NSLog(@"dispatch_async -- 3");
                obj = [[NSObject alloc] init];
            }
        });
        
        while (YES) {
            NSLog(@"dispatch_sync -- 4");
            obj = [[NSObject alloc] init];
        }
    }
}

- (void)case4 {
    dispatch_queue_t queue1 = dispatch_queue_create("com.objc.c1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("com.objc.c2", DISPATCH_QUEUE_SERIAL);
    NSMutableArray *array = [NSMutableArray array];
    dispatch_async(queue1, ^{
        NSLog(@"queue1: %@", [NSThread currentThread]);
        while (YES) {
            if (array.count < 10) {
                [array addObject:@(array.count)];
            } else {
                [array removeAllObjects];
            }
        }
    });
    
    dispatch_async(queue2, ^{
        NSLog(@"queue2: %@", [NSThread currentThread]);
        while (YES) {
            /*
             數組地址已經改變
             reason: '*** Collection <__NSArrayM: 0x6000020319b0> was mutated while being enumerated.'
             */
            for (NSNumber *number in array) {
                NSLog(@"queue2 forin array %@", number);
            }
            
            /*
             reason: '*** Collection <__NSArrayM: 0x600002072d60> was mutated while being enumerated.'
             */
            NSArray *array2 = array;
            for (NSNumber *number in array2) {
                NSLog(@"queue2 forin array2 %@", number);
            }
            
            /*
             在[NSArray copy]的時候,copy方法內部調用`initWithArray:range:copyItem:`時
             NSArray被另外一個線程清空,range不一致致使跑出異常
             reason: '*** -[__NSArrayM getObjects:range:]: range {0, 2} extends beyond bounds for empty array'
             複製過程當中數組內對象被其它線程釋放,致使訪問殭屍對象
             Thread 4: EXC_BAD_ACCESS (code=1, address=0x754822c49fc0)
             */
            NSArray *array3 = [array copy];
            for (NSNumber *number in array3) {
                NSLog(@"queue2 forin array3 %@", number);
            }
            
            /*
             複製過程當中數組內對象被其它線程釋放,致使訪問殭屍對象
             Thread 12: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
             */
            NSArray *array4 = [array mutableCopy];
            for (NSNumber *number in array4) {
                NSLog(@"queue2 forin array4 %@", number);
            }
        }
    });
}

@end
複製代碼

6.2 緣由

  1. 子線程刷新UI
  2. dispatch_group_leave 比dispatch_group_enter多
  3. 多個線程同時訪問、釋放同一對象
  4. 多線程下訪問數據:NSMutableArray、NSMutaleDictionary, NSCache是線程安全的

6.3 解決方案

多線程遇到數據同步時,須要加鎖、信號量等進行同步操做,通常多線程發生的Crash,會收到SIGSEGV。代表試圖訪問未分配給本身的內存,或視圖往沒有讀寫權限的內存中寫數據網絡

7 Wathc Dog形成的Crash

主線程耗時操做,形成主線程被卡超過必定時長,App被系統終止,通常異常編碼是0x8badf00d,一般是引用花太長時間沒法啓動、終止或響應系統事件多線程

7.1 解決方案

主線程作UI刷新和事件響應,將耗時操做(網絡請求、數據庫讀取)放到異步線程

8 後臺返回NSNull致使崩潰,多見於JAVA後臺返回

8.1 對應代碼

#import "NSNullCrashVC.h"

@interface NSNullCrashVC ()

@end

@implementation NSNullCrashVC

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor blueColor];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self case1];
}

- (void)case1 {
    /*
     reason: '-[NSNull integerValue]: unrecognized selector sent to instance 0x7fff8004b700'
     */
    NSNull *null = [[NSNull alloc] init];
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setValue:null forKey:@"key"];
    NSInteger integer = [[dict valueForKey:@"key"] integerValue];
}

@end
複製代碼
  • NULL: 用於基本數據類型,如NSInteger
  • nil : 用於OC對象
  • Nil : 用於Class類型對象的賦值(類是元類的實例,也是對象)
  • NSNull: 用於OC對象的佔位,通常會作爲集合中的佔位元素,給NSNull對象發消息會crash,後臺給咱們返回的就是NSNull對象

8.2 解決方案

利用消息轉發,參考:NullSafe, 當咱們給NSNull對象發消息,可能會崩潰(null是有內存的),給nil對象發消息,不會崩潰

#import <objc/runtime.h>
#import <Foundation/Foundation.h>

#ifndef NULLSAFE_ENABLED
#define NULLSAFE_ENABLED 1
#endif

#pragma clang diagnostic ignored "-Wgnu-conditional-omitted-operand"


@implementation NSNull (NullSafe)

#if NULLSAFE_ENABLED

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    //look up method signature
    NSMethodSignature *signature = [super methodSignatureForSelector:selector];
    if (!signature)
    {
        for (Class someClass in @[
            [NSMutableArray class],
            [NSMutableDictionary class],
            [NSMutableString class],
            [NSNumber class],
            [NSDate class],
            [NSData class]
        ])
        {
            @try
            {
                if ([someClass instancesRespondToSelector:selector])
                {
                    signature = [someClass instanceMethodSignatureForSelector:selector];
                    break;
                }
            }
            @catch (__unused NSException *unused) {}
        }
    }
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    invocation.target = nil;
    [invocation invoke];
}

#endif

@end
複製代碼

9 Socket 長鏈接致使的crash

當服務器close一個鏈接時,若client端接着發數據,根據TCP協議的規定,會收到一個RST響應,client再往這個服務器發送數據時,系統會發出一個SIGPIPE信號給進程,告訴進程這個鏈接已經斷開,不要再寫了。而根據信號的默認處理規則,SIGPIPE信號的默認執行動做是terminate(終止、退出),因此client會退出

長鏈接socket或重定向管道進入後臺,沒有變比致使崩潰的解決辦法

9.1 解決方案

  1. 切換到後臺時,關閉長鏈接和管道,回到前臺從新建立
  2. 使用signal(SIGPIPE、SIG_IGN),將SIGPIP交給系統處理,這麼作將SIGPIPE設爲SIG_IGN,使客戶端不執行默認操做,即不退出

10. 異常捕獲

即在程序崩潰前,捕獲崩潰信息,用於上報分析

10.1 原理

  1. OC類異常:NSException異常是OC代碼致使的crash。咱們能夠先經過NSGetUncaughtExceptionHandler保存先前註冊的異常處理器,而後經過NSSetUnCaughtExceptionHandler設置咱們本身的異常處理器,咱們不監控了,須要再設置回原理的異常處理器,在咱們本身的uncaughtHandleException處理器裏面,須要手動調用下原來的處理器
  2. Signal信號捕獲,Signal信號是由iOS底層mach信號異常轉換之後signal信號拋出的異常,既然是兼容posix標準的異常,咱們一樣能夠經過sigaction函數註冊對應的信號
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //OC層中被捕獲的異常,經過註冊NSUncaughtExceptionHandler捕獲異常信息
    NSSetUncaughtExceptionHandler(&uncaughtExceptionhandler);
    // 內存訪問錯誤,重複釋放等錯誤就無能爲力了,由於這種錯誤它拋出的是Signal,因此必需要專門作Signal處理
    // OC中層不能轉換的Mach異常,利用Unix標準的signal機制,註冊SIGABRT, SIGBUS, SIGSEGV等信號發生時的處理函數。
    signal(SIGSEGV, handleSignal);
    return YES;
}

static void uncaughtExceptionhandler (NSException *exception) {
    // 異常堆棧信息
    NSArray *stackArray = [exception callStackSymbols];
    // 出現異常的緣由
    NSString *reason = [exception reason];
    
    NSString *name = [exception name];
    NSString *exceptionInfo = [NSString stringWithFormat:@"Exception name: %@\nException reason: %@\nException stack: %@", name, reason, stackArray];
    NSLog(@"app delegate 異常信息: %@", exceptionInfo);
    
    //存儲到本地,下次啓動的時候上傳
    NSMutableArray *temArray = [NSMutableArray arrayWithArray:stackArray];
    [temArray insertObject:reason atIndex:0];
    [exceptionInfo writeToFile:[NSString stringWithFormat:@"%@/DOcuments/error.log", NSHomeDirectory()] atomically:YES encoding:NSUTF8StringEncoding error:nil];
}

static void handleSignal(int sig) {
    
}
複製代碼

參考

iOS中常見Crash總結

iOS 查看及導出項目運行日誌

iOS 性能優化 1、crash 監控及防崩潰處理

[譯]理解 iOS 異常類型

相關文章
相關標籤/搜索