应用异常崩溃是很正常的事情,但是应用异常崩溃信息对开发者非常重要。下面就介绍如何在iOS应用中捕获异常崩溃信息:
1. 程序启动中添加异常捕获监听函数,用来获取异常信息
NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler);
官方文档介绍:Sets the top-level error-handling function where you can perform last-minute logging before the program terminates.
UncaughtExceptionHandler是一个函数指针类型,所指向的函数需要我们实现,可以取自己想要的名字。当程序发生异常崩溃时,该函数会得到调用,这跟C,C++中的回调函数的概念是一样的。
2. 实现自己的处理函数
void UncaughtExceptionHandler(NSException *exception) {
NSArray *arr = [exception callStackSymbols];//得到当前调用栈信息
NSString *reason = [exception reason];//非常重要,就是崩溃的原因
NSString *name = [exception name];//异常类型
NSLog(@"exception type : %@ \n crash reason : %@ \n call stack info : %@", name, reason, arr);
}
3. 局限
添加异常捕获监听函数,只能监听NSException类型的异常。eg:
NSDictionary *userInfo = [[NSDictionary alloc]initWithObjectsAndKeys:@"info1", @"key1", nil];
NSException *exception = [[NSException alloc]initWithName:@"自定义异常" reason:@"自定义异常原因" userInfo:userInfo];
@throw exception;
而引起崩溃的大多数原因如:内存访问错误,重复释放等错误就无能为力了。因为这种错误它抛出的是Signal,所以必须要专门做Signal处理, 可以参考如下封装;测试时,可以调用abort()函数,模拟发送SIGABRT信号,不要联机测试,要脱机测试。
//
// MQLSignalHandler.h
// WebViewJS
//
// Created by MQL on 16/4/23.
// Copyright © 2016年 MQL. All rights reserved.
//
#import <Foundation/Foundation.h>
#include <sys/signal.h>
@interface MQLSignalHandler : NSObject
/**
* 信号处理器单例获取
*
* @return 信号处理器单例
*/
+ (instancetype)instance;
/**
* 处理异常用到的方法
*
* @param exception 自己封装的异常对象
*/
- (void)handleExceptionTranslatedFromSignal:(NSException *)exception;
@end
//
// MQLSignalHandler.m
// WebViewJS
//
// Created by MQL on 16/4/23.
// Copyright © 2016年 MQL. All rights reserved.
//
#import "MQLSignalHandler.h"
#import <UIKit/UIKit.h>
#include <libkern/OSAtomic.h>
#include <execinfo.h>
//当前处理的异常个数
volatile int32_t UncaughtExceptionCount = 0;
//最大能够处理的异常个数
volatile int32_t UncaughtExceptionMaximum = 10;
/**
* 捕获信号后的回调函数
*
* @param signo 信号变量
*/
void callbackHandlerOfCatchedSignal(int signo)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
NSMutableDictionary *userInfo =[NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signo] forKey:@"signal"];
//创建一个OC异常对象
NSException *ex = [NSException exceptionWithName:@"SignalExceptionName" reason:[NSString stringWithFormat:@"Signal %d was raised.\n",signo] userInfo:userInfo];
//处理异常消息
[[MQLSignalHandler instance] performSelectorOnMainThread:@selector(handleExceptionTranslatedFromSignal:) withObject:ex waitUntilDone:YES];
}
@interface MQLSignalHandler ()
@property BOOL isDismissed;
/**
* 注册信号处理器
*/
- (void)registerSignalHandler;
@end
@implementation MQLSignalHandler
/**
* 信号处理器单例获取
*
* @return 信号处理器单例
*/
+ (instancetype)instance{
static dispatch_once_t onceToken;
static MQLSignalHandler *s_SignalHandler = nil;
dispatch_once(&onceToken, ^{
if (s_SignalHandler == nil) {
s_SignalHandler = [[MQLSignalHandler alloc] init];
[s_SignalHandler registerSignalHandler];
}
});
return s_SignalHandler;
}
/**
* 注册信号处理器
*/
- (void)registerSignalHandler
{
//注册程序由于abort()函数调用发生的程序中止信号
signal(SIGABRT, callbackHandlerOfCatchedSignal);
//注册程序由于非法指令产生的程序中止信号
signal(SIGILL, callbackHandlerOfCatchedSignal);
//注册程序由于无效内存的引用导致的程序中止信号
signal(SIGSEGV, callbackHandlerOfCatchedSignal);
//注册程序由于浮点数异常导致的程序中止信号
signal(SIGFPE, callbackHandlerOfCatchedSignal);
//注册程序由于内存地址未对齐导致的程序中止信号
signal(SIGBUS, callbackHandlerOfCatchedSignal);
//程序通过端口发送消息失败导致的程序中止信号
signal(SIGPIPE, callbackHandlerOfCatchedSignal);
}
//处理异常用到的方法
- (void)handleExceptionTranslatedFromSignal:(NSException *)exception
{
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"程序出现问题啦" message:@"崩溃信息" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:nil];
[alertView show];
//当接收到异常处理消息时,让程序开始runloop,防止程序死亡
while (!_isDismissed) {
for (NSString *mode in (__bridge NSArray *)allModes)
{
CFRunLoopRunInMode((CFStringRef)mode, 0, true);
}
}
//当点击弹出视图的Cancel按钮哦,isDimissed = YES,上边的循环跳出
CFRelease(allModes);
//拦截处理后,执行默认处理
signal(SIGABRT, SIG_DFL);
signal(SIGILL, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
}
- (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex
{
//因为这个弹出视图只有一个Cancel按钮,所以直接进行修改isDimsmissed这个变量了
_isDismissed = YES;
}
@end