@(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
生成屬性的setter
和getter
方法 - 5、在
MRC
模式下,變數的retain
和release
要謹慎,建議採用安全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
使用命令:
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_async
、CGD組的dispatch_group_enter
和dispatch_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、多執行緒下非執行緒安全類的使用,如
NSMutableArray
、NSMutableDictionary
。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物件傳送訊息不會crashNil
:用於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中的crash防護(一)unrecognized selector sent to instance
小蘿莉說Crash(一):Unrecognized selector sent to instance xxxx
GitHub開源實現: