程序都會有bug,那么在有bug和異常時能不能給個提醒呢,今天咱們來看看這個問題怎么解決
首先說下基本的NSException:
// 先創建一個異常對象
NSException * exception = [NSException exceptionWithName:@"test" reason:@"This is test" userInfo:nil];
@try {
//可能拋出異常的代碼
int b = 0;
switch (b)
{
case 0:
@throw exception;//b=0,則拋出異常;
break;
default:
break;
}
} @catch (NSException *exception) {
//異常處理的代碼
NSLog(@"exception.name= %@" ,exception.name);
NSLog(@"exception.reason= %@" ,exception.reason);
NSLog(@"b==0 Exception!");
}
@finally {
//不論是否有異常總會被執行的代碼,通常用于clean
NSLog(@"finally!");
}
- 平常我們直接在代碼中用的最多還是,
@try -- @catch -- @finally
, 另外NSExcetpion
還可以對其進行Custom,繼承并擴展使用它。
UncaughtExceptionHandler
1、首先需要在appDelegate
中使用InstallUncaughtExceptionHandler()
用于監聽
2、添加UncaughtExceptionHandler
這個類
- iOS SDK提供的函數是
NSSetUncaughtExceptionHandler
來進行異常處理。但是無法處理內存訪問錯誤、重復釋放等錯誤,因為這些錯誤發送的signal。所以需要處理這些signal
void InstallUncaughtExceptionHandler(void) {
NSSetUncaughtExceptionHandler(&HandleException);
signal(SIGABRT, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGFPE, SignalHandler);
signal(SIGBUS, SignalHandler);
signal(SIGPIPE, SignalHandler);
}
注意不同情況的 signal , 這等同于不同情況的異常。
void HandleException(NSException *exception)
{
// 遞增的一個全局計數器,很快很安全,防止并發數太大
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum) return;
// 獲取 堆棧信息的數組
NSArray *callStack = [UncaughtExceptionHandler backtrace];
// 設置該字典
NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
// 給 堆棧信息 設置 地址 Key
[userInfo
setObject:callStack
forKey:UncaughtExceptionHandlerAddressesKey];
// 假如崩潰了執行 handleException: ,并且傳出 NSException
[[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject:[NSException exceptionWithName:[exception name] reason:[exception reason]
userInfo:userInfo]
waitUntilDone:YES];
}
void SignalHandler(int signal)
{
// 遞增的一個全局計數器,很快很安全,防止并發數太大
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum) return;
// 設置是哪一種 single 引起的問題
NSMutableDictionary *userInfo =
[NSMutableDictionary
dictionaryWithObject:[NSNumber numberWithInt:signal]
forKey:UncaughtExceptionHandlerSignalKey];
// 獲取堆棧信息數組
NSArray *callStack = [UncaughtExceptionHandler backtrace];
// 寫入地址
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
// 假如崩潰了執行 handleException: ,并且傳出 NSException
[[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:)
withObject: [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName reason: [NSString stringWithFormat:
NSLocalizedString(@"Signal %d was raised.", nil),signal]
userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey]] waitUntilDone:YES];
}
void HandleException(NSException *exception)
{
// 遞增的一個全局計數器,很快很安全,防止并發數太大
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum) return;
// 獲取 堆棧信息的數組
NSArray *callStack = [UncaughtExceptionHandler backtrace];
// 設置該字典
NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
// 給 堆棧信息 設置 地址 Key
[userInfo
setObject:callStack
forKey:UncaughtExceptionHandlerAddressesKey];
// 假如崩潰了執行 handleException: ,并且傳出 NSException
[[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject:[NSException exceptionWithName:[exception name] reason:[exception reason]
userInfo:userInfo]
waitUntilDone:YES];
}
需要注意OSAtomicIncrement32
的使用,它是一個遞增的一個全局計數器,效果又快又安全,是為了防止并發數太大出現錯誤的情況。
+ (NSArray *)backtrace
{
void* callstack[128];
// 該函數用來獲取當前線程調用堆棧的信息,獲取的信息將會被存放在buffer中(callstack),它是一個指針數組。
int frames = backtrace(callstack, 128);
// backtrace_symbols將從backtrace函數獲取的信息轉化為一個字符串數組.
char **strs = backtrace_symbols(callstack, frames);
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (
int i = UncaughtExceptionHandlerSkipAddressCount;
i < UncaughtExceptionHandlerSkipAddressCount + UncaughtExceptionHandlerReportAddressCount;
i++)
{
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs); // 記得free
return backtrace;
}
這邊值得注意的是下面這兩個函數方法
int backtrace(void**,int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
char** backtrace_symbols(void* const*,int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
該函數用來獲取當前線程調用堆棧的信息,并且轉化為字符串數組。
最后再來處理,此處涉及到 CFRunLoopRunInMode, kill 值得注意!
- (void)handleException:(NSException *)exception
{
// 打印或彈出框
// TODO :
// 接到程序崩潰時的信號進行自主處理
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!dismissed)
{
for (NSString *mode in (NSArray *)allModes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
CFRelease(allModes);
// 下面等同于清空之前設置的
NSSetUncaughtExceptionHandler(NULL);
signal(SIGABRT, SIG_DFL);
signal(SIGILL, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
// 殺死 或 喚起
if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName]) {
kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
} else {
[exception raise];
}
}
- 不足的是,并不是所有的程序崩潰都是能可以捕捉到異常的,有些時候是因為內存等一些其他的錯誤導致程序的崩潰,這樣的情況就不能通過這個方法直接獲取到啦。 同時Apple并不建議我們拋出異常,耗費的資源太大,所以此處作為了解學習比較適宜。