IOS崩溃(NSSetUncaughtExceptionHandler)

转自http://blog.csdn.net/arthurchenjs/article/details/7043276和触控科技。

IOS SDK中提供了一个现成的函数 NSSetUncaughtExceptionHandler 用来做异常处理,如果是在调试的过程中,异常的信息是一目了然,但是如果是在已经发布的程序中,获取异常的信息有时候是比较困难的, iOS提供了异常发生的处理API,我们在程序启动的时候可以添加这样的Handler,这样的程序发生异常的时候就可以对这一部分的信息进行必要的处理,适时的反馈给开发者。

我做了一个简单的类,进行很基本的操作,可以添加和获取Handler,捕获到异常后将信息写入到app的Documens下的Exception.txt中,其实还有很多的处理方法。

 比如可以在程序下一次起来的时候读取这个异常文件发生到服务端。

 或者直接就是在处理代码中用openurl的方式(mailto:)调用发送邮件的方式,将异常信息直接变成邮件发送到指定地址。

 以下是完整的代码实现:

 

#pragma mark -

#pragma mark Application lifecycle   

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {      

    // Override point for customization after application launch.

     [window makeKeyAndVisible];       

     [NdUncaughtExceptionHandler setDefaultHandler];

     NSArray *array = [NSArray arrayWithObject:@"there is only one objective in this arary,call index one, app will crash and throw an exception!"];

     NSLog(@"%@", [array objectAtIndex:1]);

     return YES;

}

  

#import <Foundation/Foundation.h>

@interface NdUncaughtExceptionHandler : NSObject {

          

 }

+ (void)setDefaultHandler;           

+ (NSUncaughtExceptionHandler*)getHandler;

@end

//还可以选择设置自定义的handler,让用户取选择

#import "NdUncaughtExceptionHandler.h"

 NSString *applicationDocumentsDirectory() {       

    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];         

}        

-(void) UncaughtExceptionHandler(NSException *exception) {

     NSArray *arr = [exception callStackSymbols];

     NSString *reason = [exception reason];

     NSString *name = [exception name];

     NSString *url = [NSString stringWithFormat:@"========异常崩溃报=====\nname:\n%@\nreason: \n%@\ncallStackSymbols:\n%@",name,reason,[arr componentsJoinedByString:@"\n"]];

     NSString *path = [applicationDocumentsDirectory() stringByAppendingPathComponent:@"Exception.txt"];

     [url writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];

     //除了可以选择写到应用下的某个文件,通过后续处理将信息发送到服务器等

     //还可以选择调用发送邮件的的程序,发送信息到指定的邮件地址(

        

           -(void) UncaughtExceptionHandler(NSException *exception) {
            
                      NSArray *arr = [exceptioncallStackSymbols];

                      NSString *reason = [exception reason];

                      NSString *name = [exception name];

                  

                      NSString *urlStr = [NSString stringWithFormat:@"mailto://kobecn@gmail.com?subject=bug报告&body=感谢您的配合!<br><br><br>" "错误详情:<br>%@<br>---------<br>%@<br>------<br>%@",name,reason,[arr componentsJoinedByString:@"<br>"]];        

                      NSURL *url = [NSURL URLWithString:[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];    

                 [[UIApplication shareApplication] openURL:url];

            }
          
            然后在delegate文件里面
 
            -(BOOL)application:(UIApplication*)applicationdidFinishLaunchingWithOptions:(NSDictionary*)launchOptions{
           
            NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
           }

      )

     //或者调用某个处理程序来处理这个信息        

}

   

@implementation NdUncaughtExceptionHandler

-(NSString *)applicationDocumentsDirectory {

    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];         

}

+ (void)setDefaultHandler {

     NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler);         

}

+ (NSUncaughtExceptionHandler*)getHandler {     

     return NSGetUncaughtExceptionHandler();         

}

@end

 

 

=============异常崩溃报告=============

name:

NSRangeException  

reason:

*** -[NSArray objectAtIndex:]: index 1 beyond bounds [0 .. 0]

callStackSymbols:

0   CoreFoundation                      0x02393919 __exceptionPreprocess + 185

1   libobjc.A.dylib                     0x024e15de objc_exception_throw + 47

2   CoreFoundation                      0x0238958c -[__NSArrayI objectAtIndex:] + 236

3   UncaughtE                           0x000022e8 -[UncaughtEAppDelegate application:didFinishLaunchingWithOptions:] + 15

4   UIKit                               0x002b8543 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163

5   UIKit                               0x002ba9a1 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 346

6   UIKit                               0x002c4452 -[UIApplication handleEvent:withNewEvent:] + 1958

7   UIKit                               0x002bd074 -[UIApplication sendEvent:] + 71

8   UIKit                               0x002c1ac4 _UIApplicationHandleEvent + 7495

9   GraphicsServices                    0x02bf9afa PurpleEventCallback + 1578

10  CoreFoundation                      0x02374dc4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 52

11  CoreFoundation                      0x022d5737 __CFRunLoopDoSource1 + 215

12  CoreFoundation                      0x022d29c3 __CFRunLoopRun + 979

13  CoreFoundation                      0x022d2280 CFRunLoopRunSpecific + 208

14  CoreFoundation                      0x022d21a1 CFRunLoopRunInMode + 97

15  UIKit                               0x002ba226 -[UIApplication _run] + 625

16  UIKit                               0x002c5b58 UIApplicationMain + 1160

17  UncaughtE                           0x00002228 main + 102

18  UncaughtE                           0x000021b9 start + 53

 

不足的地方是,并不是所有的程序崩溃都是由于发生可以捕捉的异常的,有些时候引起崩溃的大多数原因如:内存访问错误,重复释放等错误就无能为力了,因为这种错误它抛出的是Signal,所以必须要专门做Signal处理。首先定义一个UncaughtExceptionHandler类,.h头文件的代码如下:

UncaughtExceptionHandler类,.h头文件的代码如下:

1
2
3
4
5
6
#import
 
@interface UncaughtExceptionHandler : NSObject{
BOOL dismissed;
}
@end
1
void InstallUncaughtExceptionHandler();

然后在.mm文件实现InstallUncaughtExceptionHandler(),如下:

1
2
3
4
5
6
7
8
9
void InstallUncaughtExceptionHandler(){
 
signal(SIGABRT, MySignalHandler);
signal(SIGILL, MySignalHandler);
signal(SIGSEGV, MySignalHandler);
signal(SIGFPE, MySignalHandler);
signal(SIGBUS, MySignalHandler);
signal(SIGPIPE, MySignalHandler);
}

这样,当应用发生错误而产生上述Signal后,就将会进入我们自定义的回调函数MySignalHandler。为了得到崩溃时的现场信息,还可以加入一些获取CallTrace及设备信息的代码,.mm文件的完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#import "UncaughtExceptionHandler.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 UncaughtExceptionHandler
 
+ (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;
 
    }
 
}
 
- (void)handleException:(NSException *)exception
 
{
 
    UIAlertView *alert =
 
        [[[UIAlertView alloc]
 
            initWithTitle:NSLocalizedString(@"Unhandled exception", nil)
 
            message:[NSString stringWithFormat:NSLocalizedString(
 
                @"You can try to continue but the application may be unstable.\n"
 
                @"%@\n%@", nil),
 
                [exception reason],
 
                [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]
 
            delegate:self
 
            cancelButtonTitle:NSLocalizedString(@"Quit", nil)
 
            otherButtonTitles:NSLocalizedString(@"Continue", nil), nil]
 
        autorelease];
 
    [alert show];  
 
    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];
 
    }
 
}
 
@end

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
NSString* getAppInfo() { NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\nUDID : %@\n", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], [UIDevice currentDevice].model, [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion, [UIDevice currentDevice].uniqueIdentifier]; NSLog(@"Crash!!!! %@", appInfo); return appInfo; } void MySignalHandler(int signal) { int32_t exceptionCount = OSAtomicIncrement32(&amp;UncaughtExceptionCount); if (exceptionCount &gt; UncaughtExceptionMaximum)
{
return;
}
 
NSMutableDictionary *userInfo =
 
[NSMutableDictionary
 
dictionaryWithObject:[NSNumber numberWithInt:signal]
 
forKey:UncaughtExceptionHandlerSignalKey];
 
NSArray *callStack = [UncaughtExceptionHandler backtrace];
 
[userInfo
 
setObject:callStack
 
forKey:UncaughtExceptionHandlerAddressesKey];
 
[[[[UncaughtExceptionHandler alloc] init] autorelease]
 
performSelectorOnMainThread:@selector(handleException:)
 
withObject:
 
[NSException
 
exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
 
reason:
 
[NSString stringWithFormat:
 
NSLocalizedString(@"Signal %d was raised.\n"
 
@"%@", nil),
 
signal, getAppInfo()]
 
userInfo:
 
[NSDictionary
 
dictionaryWithObject:[NSNumber numberWithInt:signal]
 
forKey:UncaughtExceptionHandlerSignalKey]]
 
waitUntilDone:YES];
 
}
 
void InstallUncaughtExceptionHandler()
 
{
 
signal(SIGABRT, MySignalHandler);
 
signal(SIGILL, MySignalHandler);
 
signal(SIGSEGV, MySignalHandler);
 
signal(SIGFPE, MySignalHandler);
 
signal(SIGBUS, MySignalHandler);
 
signal(SIGPIPE, MySignalHandler);
 
}

在应用自身的 didFinishLaunchingWithOptions 前,加入一个函数:

1
2
3
4
- (void)installUncaughtExceptionHandler
{
InstallUncaughtExceptionHandler();
}

最后,在 didFinishLaunchingWithOptions 中加入这一句代码就行了:

1
[self InstallUncaughtExceptionHandler];

现在,基本上所有崩溃都能Hold住了。崩溃时将会显示出如下的对话框:

iOS开发23:通过归档永久存储数据 - 双子座的个人页面 - 开源中国社区

这样在崩溃时还能从容地弹出对话框,比起闪退来,用户也不会觉得那么不爽。然后在下次启动时还可以通过邮件来发送Crash文件到邮箱,这就看各个应用的需求了。

     

    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值