宝宝再也不用担心APP闪退了

以前天天没事逛知乎,现在没事就来逛简书,发现生活真真真的美好。

去年的简书貌似是以社科类的文章为主,技术类的文章想要上首页,那是特别特别难得,不过现在很明显看到很多技术大神的文章活跃在首页,他们技术不错,文笔也不差,写得东西都挺新鲜,与时俱进,相信很多人像我一样在此都得到了不少帮助。虽然我经常逛知乎,可是我真的做到了一个回答也没有,一个提问也没有的记录,真的是一个超级路人甲,可是我怎么就在这里留下了脚印呢?!(可能是简书给我一种很简约,有韵味的感觉,那就是我内心深处所向往的吧......who know......)

我们的项目从零到有之后,他们觉得APP偶尔闪退体验不好,可能是因为后台变化数据格式导致的崩溃,或者是因为ProtocolBuffer的类库中隐患,或者就是我写的代码有问题。 那么我能不能捕捉这样的异常呢?把异常告诉用户,总比突然就退出来得优雅吧。

去百度一下NSException,还真有一些比较好的方法。
我总结了以下两种方法:
第一种方法未经过封装,容易理解其中的原理
第二种方法已封装好(感谢那位大神,代码写的很好,有的地方我确实看不懂,还需努力学习,crying~~~),需要花时间理解底层实现,但是使用起来方便简单,推荐使用

第一种方法:

主要使用:

@try
{
 
}
@catch (NSException *exception)
{

}
@finally
{

}
  • 首先自定义一个捕捉异常的类,继承自NSException,
    .h文件代码如下:
8B03FDBA-A579-46C2-9DF0-0FDEEA4D6AFD.png

.m文件代码如下:

CE81FC74-F8DD-4A69-89CC-92311DBBD12C.png

没错,你看到了,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");
}
3D528682-A33C-4A26-BB5A-99C7A50BCEF7.png
  • command+R试试看
4D3B22DA-7501-4CF2-B9D4-A6B810EF07D6.png

提示了这个异常,并且APP还是可以继续操作,基本改善了APP的状况
但是这个方法不够好用,必须要把方法放进@try里面,当然很难做到,如果对其封装,可能能提高它的实用性

其实NSException这个类里面还有很多东西可以用,有的是Apple Inc已经封装好了,所以可以继续深入研究,一定还有我们没发现的东西,希望需要有发现的宝宝们乐于分享哇!

第二种方法:

  • 下面这个方法,我就展示代码然后说说怎么用,里面封装的比较多,有点C和C++底层的感觉,懂的多的宝宝可以指点下哇,不懂的宝宝就默默拿用吧~~~

  • 先自定义一个捕捉异常的类,具体如下
    .h文件如下:

#import <Foundation/Foundation.h>
@interface RLUncaughtExceptionHandler : NSObject
{
    BOOL dismissed;
}
void HandleException(NSException *exception);
void SignalHandler(int signal);
void InstallUncaughtExceptionHandler(void);
@end

.m文件很长很长:

#import "RLUncaughtExceptionHandler.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>

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:
4F744FAB-1D3E-4285-B357-54989B0B5982.png

如果点退出的话就是退出APP,如果点继续,用户可以继续做其他的操作

当然实际上,你会给用户一个选项,发送崩溃信息给后台,这样后台能够收到用户发送的崩溃记录,开发者能够通过记录进行调试代码。

就这样,APP基本上不会闪退了,这样的APP用起来流畅多了!


update 20161121
Demo演示

CrashDeom.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,478评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,825评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,482评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,726评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,633评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,018评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,168评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,320评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,264评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,288评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,995评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,587评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,909评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,284评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,862评论 2 339

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,263评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,531评论 18 399
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,977评论 4 60
  • 天气在忽冷忽热中反复,前几天,穿个外套都冷!这几天,穿个蕾丝的都热! 热燥首先引起的是火大,所有的细胞都被...
    旭之梦阅读 503评论 3 1
  • 佳明喜欢慧慧很久了。 他是从什么时候喜欢小雪的呢?他自己也说不清了,也许因为在开学以后,看到了慧慧纤细的腰肢,楚楚...
    刘小松阅读 315评论 0 1