以前天天没事逛知乎,现在没事就来逛,发现生活真真真的美好。
去年的貌似是以社科类的文章为主,技术类的文章想要上首页,那是特别特别难得,不过现在很明显看到很多技术大神的文章活跃在首页,他们技术不错,文笔也不差,写得东西都挺新鲜,与时俱进,相信很多人像我一样在此都得到了不少帮助。虽然我经常逛知乎,可是我真的做到了一个回答也没有,一个提问也没有的记录,真的是一个超级路人甲,可是我怎么就在这里留下了脚印呢?!(可能是给我一种很简约,有韵味的感觉,那就是我内心深处所向往的吧......who know......)
我们的项目从零到有之后,他们觉得APP偶尔闪退体验不好,可能是因为后台变化数据格式导致的崩溃,或者是因为ProtocolBuffer的类库中隐患,或者就是我写的代码有问题。 那么我能不能捕捉这样的异常呢?把异常告诉用户,总比突然就退出来得优雅吧。
去百度一下NSException,还真有一些比较好的方法。
我总结了以下两种方法:
第一种方法未经过封装,容易理解其中的原理
第二种方法已封装好(感谢那位大神,代码写的很好,有的地方我确实看不懂,还需努力学习,crying~~~),需要花时间理解底层实现,但是使用起来方便简单,推荐使用
第一种方法:
主要使用:
@try
{
}
@catch (NSException *exception)
{
}
@finally
{
}
- 首先自定义一个捕捉异常的类,继承自NSException,
.h文件代码如下:
.m文件代码如下:
没错,你看到了,noSuchMethod这个方法没有实现,我们需要调用此方法来测试
- 然后把以下这个方法写在Appdelegate里面
@try//在这里面放入需要进行捕捉异常的方法
{
[vc noSuchMethod];
NSLog(@"上面这个方法实际上只是在.h文件中声明,但是.m文件并没有实现,现在调用这个方法必然会导致程序崩溃,也就是闪退");
}
@catch (NSException *exception)//一旦捕捉到异常就会进入这个方法里面,对捕捉到的异常进行处理
{
NSLog(@"Caught %@%@", [exception name], [exception reason]);
[RLException popAlertWithTitle:@"错误信息提示" message:[NSString stringWithFormat:@"%@\n%@",[exception name],[exception reason]]];
NSLog(@"已将异常反馈到APP上,用户会受到这个提示,而且APP不会闪退,可以继续使用APP)");
}
@finally// 异常发生或者不发生,这里都会执行
{
RLLog(@"finally");
}
- command+R试试看
提示了这个异常,并且APP还是可以继续操作,基本改善了APP的状况
但是这个方法不够好用,必须要把方法放进@try里面,当然很难做到,如果对其封装,可能能提高它的实用性
其实NSException这个类里面还有很多东西可以用,有的是Apple Inc已经封装好了,所以可以继续深入研究,一定还有我们没发现的东西,希望需要有发现的宝宝们乐于分享哇!
第二种方法:
下面这个方法,我就展示代码然后说说怎么用,里面封装的比较多,有点C和C++底层的感觉,懂的多的宝宝可以指点下哇,不懂的宝宝就默默拿用吧~~~
先自定义一个捕捉异常的类,具体如下
.h文件如下:
#import
@interface RLUncaughtExceptionHandler : NSObject
{
BOOL dismissed;
}
void HandleException(NSException *exception);
void SignalHandler(int signal);
void InstallUncaughtExceptionHandler(void);
@end
.m文件很长很长:
#import "RLUncaughtExceptionHandler.h"
#include
#include
NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";
volatile int32_t UncaughtExceptionCount = 0;
const int32_t UncaughtExceptionMaximum = 10;
const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger UncaughtExceptionHandlerReportAddressCount = 5;
@implementation RLUncaughtExceptionHandler
+ (NSArray *)backtrace
{
void* callstack[128];
int frames = backtrace(callstack, 128);
char **strs = backtrace_symbols(callstack, frames);
int i;
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (
i = UncaughtExceptionHandlerSkipAddressCount;
i < UncaughtExceptionHandlerSkipAddressCount +
UncaughtExceptionHandlerReportAddressCount;
i++)
{
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
- (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex
{
if (anIndex == 0)
{
dismissed = YES;
}else if (anIndex==1) {
NSLog(@"ssssssss");
}
}
- (void)validateAndSaveCriticalApplicationData
{
}
- (void)handleException:(NSException *)exception
{
[self validateAndSaveCriticalApplicationData];
UIAlertView *alert =
[[UIAlertView alloc]
initWithTitle:NSLocalizedString(@"抱歉,程序出现了异常", nil)
message:[NSString stringWithFormat:NSLocalizedString(
@"如果点击继续,程序有可能会出现其他的问题,建议您还是点击退出按钮并重新打开\n\n"
@"异常原因如下:\n%@\n%@", nil),
[exception reason],
[[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]
delegate:self
cancelButtonTitle:NSLocalizedString(@"退出", nil)
otherButtonTitles:NSLocalizedString(@"继续", nil), nil];
// UIAlertView *alert =
// [[UIAlertView alloc]
// initWithTitle:@"抱歉,程序出现了异常"
// message:[NSString stringWithFormat:@"如果点击继续,程序有可能会出现其他的问题,建议您还是点击退出按钮并重新打开\n\n"
// @"异常原因如下:\n%@\n%@",
// [exception reason],
// [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]
// delegate:self
// cancelButtonTitle:@"退出"
// otherButtonTitles:@"继续", nil];
// dispatch_async(dispatch_get_main_queue(), ^{
[alert show];
// });
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!dismissed)
{
for (NSString *mode in (__bridge 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];
}
}
@end
void HandleException(NSException *exception)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
NSArray *callStack = [RLUncaughtExceptionHandler backtrace];
NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo
setObject:callStack
forKey:UncaughtExceptionHandlerAddressesKey];
[[[RLUncaughtExceptionHandler 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;
}
NSMutableDictionary *userInfo =
[NSMutableDictionary
dictionaryWithObject:[NSNumber numberWithInt:signal]
forKey:UncaughtExceptionHandlerSignalKey];
NSArray *callStack = [RLUncaughtExceptionHandler backtrace];
[userInfo
setObject:callStack
forKey:UncaughtExceptionHandlerAddressesKey];
[[[RLUncaughtExceptionHandler 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 InstallUncaughtExceptionHandler(void)
{
NSSetUncaughtExceptionHandler(&HandleException);
signal(SIGABRT, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGFPE, SignalHandler);
signal(SIGBUS, SignalHandler);
signal(SIGPIPE, SignalHandler);
}
- 然后在Appdelegate里面只需调用捕捉异常类的一个方法,只要整个程序有任何地方崩溃,它都能够捕捉到:(然后你在任何一个文件中写一个会导致程序崩溃的方法,比如给一个Button添加方法,但是并没有实现方法)
-(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
InstallUncaughtExceptionHandler();
return YES;
}
- 开始command+R:
如果点退出的话就是退出APP,如果点继续,用户可以继续做其他的操作
当然实际上,你会给用户一个选项,发送崩溃信息给后台,这样后台能够收到用户发送的崩溃记录,开发者能够通过记录进行调试代码。
就这样,APP基本上不会闪退了,这样的APP用起来流畅多了!
update 20161121
Demo演示