作者 | 在路上重名了啊
@(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;
[self.delegate notImplementionFunc];
if ( [self.delegate respondsToSelector:@selector(notImplementionFunc)] ) {
[self.delegate notImplementionFunc];
}
}
- (void)case2 {
NSMutableArray* array = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
self.mutableArray = array;
[self.mutableArray addObject:@4];
}
- (void)case3 {
if (@available(iOS 10.0, *)) {
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
}];
} else {
}
}
@end
找不到方法iOS系統拋出異常崩潰
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
圖片過大請點擊查看
圖片摘自:Objective-C 消息發送與轉發機制原理
使用命令: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];
}
- (void)case1 {
NSObject* obj = [[NSObject alloc]init];
[obj setValue:@"value" forKey:@"key"];
}
- (void)case2 {
KvcCrashVCObj* obj = [[KvcCrashVCObj alloc]init];
[obj setValue:nil forKey:@"name"];
[obj setValue:@"value" forKey:nil];
}
- (void)case3 {
KvcCrashVCObj* obj = [[KvcCrashVCObj alloc]init];
[obj setValue:nil forKey:@"falseKey"];
}
@end
給不存在的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;
}
經過ARC的洗禮之後,普通的訪問釋放對象產生的EXC_BAD_ACCESS已經大量減少了,現在出現的EXC_BAD_ACCESS有很大一部分來自malloc的對象或者越界訪問。
#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);
}
- (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];
}
- (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;
}
[self.view addSubview:self.weakView];
[self.view addSubview:self.unSafeView];
[self.view addSubview:self.assignView];
[self.view addSubview:self.associateView];
}
@end
出現懸掛指針,對象沒有被初始化,或者訪問的對象被釋放
1、Debug階段開啟殭屍模式,Release時關閉殭屍模式
2、使用Xcode的Address Sanitizer檢查地址訪問越界
3、創建對象的時候記得初始化
4、對象的屬性使用正確的修飾方式(strong/weak)
5、調用block的時候,做判斷
#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];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self func4];
}
- (void)func1 {
KvoCrashVCObj* obj = [[KvoCrashVCObj alloc] init];
[self addObserver:obj
forKeyPath:@"view"
options:NSKeyValueObservingOptionNew
context:nil];
self.view = [[UIView alloc] init];
}
- (void)func2 {
KvoCrashVCObj* obj = [[KvoCrashVCObj alloc] init];
[obj addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew
context:nil];
obj.name = @"";
}
- (void)func3 {
[self.sObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
self.sObj.name = @"0";
}
- (void)func4 {
[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);
}
@end
添加了觀察者,沒有在正確的時機移除
1、addObserver和removeObserver一定要成對出現,
2、推薦使用FaceBook開源的第三方庫 FBKVOController
#import "CollectionCrashVC.h"
@interface CollectionCrashVC ()
@end
@implementation CollectionCrashVC
- (void)viewDidLoad {
[super viewDidLoad];
[self case4];
}
- (void)case1 {
NSArray* array = [[NSArray alloc]initWithObjects:@1, @2, @3, nil];
NSNumber* number = [array objectAtIndex:4];
NSLog(@"number = %@", number);
}
- (void)case2 {
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];
}
}];
for (NSNumber* obj in array) {
if (obj.integerValue == 2) {
[array removeObject:obj];
}
}
}
- (void)case4 {
NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:@1 forKey:@1];
[dictionary setValue:nil forKey:@1];
[dictionary setObject:nil forKey:@1];
}
@end
越界、添加nil、多線程非原子性操作、遍歷的同時移除元素
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
#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];
}
- (void)case1 {
dispatch_group_t group = dispatch_group_create();
dispatch_group_leave(group);
}
- (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];
}
}
}
-(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) {
for (NSNumber* number in array) {
NSLog(@"%@", number);
}
NSArray* immutableArray = array;
for (NSNumber* number in immutableArray) {
NSLog(@"%@", number);
}
NSArray* immutableArray1 = [array copy];
for (NSNumber* number in immutableArray1) {
NSLog(@"%@", number);
}
NSArray* immutableArray2 = [array mutableCopy];
for (NSNumber* number in immutableArray2) {
NSLog(@"%@", number);
}
}
});
}
@end
死鎖、子線程中更新UI、多個線程同時釋放一個對象
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信號,表明試圖訪問未分配給自己的內存, 或試圖往沒有寫權限的內存地址寫數據。
當伺服器close一個連接時,若client端接著發數據。根據TCP協議的規定,會收到一個RST響應,client再往這個伺服器發送數據時,系統會發出一個SIGPIPE信號給進程,告訴進程這個連接已經斷開了,不要再寫了。而根據信號的默認處理規則,SIGPIPE信號的默認執行動作是terminate(終止、退出),所以client會退出。
長連接socket或重定向管道進入後臺,沒有關閉導致崩潰的解決辦法:
7.1、解決方案:方法一:1、切換到後臺是,關閉長連接和管道,回到前臺重新創建。
方法二:2、使用signal(SIGPIPE,SIG_IGN),將SIGPIP交給系統處理,這麼做將SIGPIPE設為SIG_IGN,使客戶端不執行默認操作,即不退出。
主線程執行耗時操作,導致主線程被卡超過一定時間。一般異常編碼是0x8badf00d,表示應用發生watch dog超時而被iOS終止,通常是應用花費太多的時間無法啟動、終止或者響應系統事件。
主線程只負責更新UI和事件響應,將耗時操作(網絡請求、資料庫讀寫等)異步放到後臺線程執行。
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對象
利用消息轉發。參考:NullSafe。當我們給一個NSNull對象發送消息的話,可能會崩潰(null是有內存的),而發送給nil的話,是不會崩潰的。
崩潰主要是由於 Mach 異常、Objective-C 異常(NSException)引起的,同時對於 Mach 異常,到了 BSD 層會轉換為對應的 Signal 信號,那麼我們也可以通過捕獲信號,來捕獲 Crash 事件。針對 NSException 可以通過註冊 NSUncaughtExceptionHandler 捕獲異常信息。
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];
[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];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailPath]];
}
static void handleSignal( int sig ) {
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSSetUncaughtExceptionHandler(&uncaught_exception_handler);
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開源實現:
< END >
過去一個月最火的 10 個 Swift 開源項目
SwiftUI 的 DSL 語法分析
基於 JS 的高性能 Flutter 動態化框架 MXFlutter