如何实现全局 iPhone 异常处理?

62

我在我的iPhone应用程序中遇到了一个崩溃问题,它会抛出一个NSException异常。崩溃报告中并没有明确指出错误出现的位置和具体引起崩溃的原因。有没有什么聪明的方法让我设置一个顶层异常处理程序来查看是什么导致了这个问题?我自己无法复制这个问题,但我的几个测试用户却可以。

如何聪明地处理这种情况的问题?

6个回答

85

看起来你在这里问了两个问题:如何设置顶层异常处理程序;以及如何解决确定根本原因的问题。

捕获异常可以用几种不同的方式,但对于此问题,最好的方法似乎是使用 NSSetUncaughtExceptionHandler 设置异常处理程序。

当应用程序中出现异常时,它将由默认异常处理程序处理。该处理程序仅在应用程序关闭之前向控制台记录一条消息。您可以通过使用上述函数设置自定义异常处理程序来覆盖此行为。最好的位置是在应用委托 applicationDidFinishLaunching: 方法中进行设置。

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
    NSSetUncaughtExceptionHandler(&myExceptionHandler);
}

一旦你设置了自定义处理程序,你就希望在帮助你确定原因方面扩展默认输出。

void myExceptionHandler(NSException *exception)
{
    NSArray *stack = [exception callStackReturnAddresses];
    NSLog(@"Stack trace: %@", stack);
}

不幸的是,与OSX相比,iPhone在生成漂亮的堆栈跟踪方面似乎非常有限。上面的代码会产生一些看似垃圾的输出;但是,您可以通过atos工具运行此输出,并应该能够从中生成有用的堆栈跟踪。

另一个选项是按照这篇文章的说明自动产生漂亮的堆栈跟踪。

由于这将发送给beta测试人员,您可能需要调整一下才能使其对您起作用。

您说您自己无法复制该问题,只有您的用户发生了问题。在这种情况下,您可能会发现苹果公司的这篇技术说明有用:

更新:虽然这篇文章仍包含有用的信息,但其中一些链接已经永久失效。建议使用这篇替代文章中的信息。


2
为什么要自己遍历堆栈,当你可以使用 NSLog(@"%@" stack) ? - AlBlue
我的异常处理程序需要运行多少个周期?我试图显示一个UIAlertView,但应用程序在它显示之前完全崩溃了。你能强制myExceptionHandler占用更多的周期吗? - Mark
这不应该是一个函数吗,即 void myExceptionHandler(NSException *exception)? - Grady Player
@mark 或者你可以将一个布尔值写入NSUserDefaults,例如applicationDidCrash = YES。然后下次应用程序启动时,检查该标志并显示警报并运行任何必要的代码。 - nrj
复活死链接:rel.me的链接似乎已经失效了。有人知道其他的地址吗? - Rob van der Veer
显示剩余4条评论

12

如果您打算自己完成,可以使用以下其中一种方法之一

方法1:

void onUncaughtException(NSException* exception)
{
//save exception details
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSSetUncaughtExceptionHandler(&onUncaughtException);
  //Add coding to find if any exception has occurred from saved details if so send it to server or ask user to comment on the issue.
//Rest of the coding
}

方法二:

void onUncaughtException(NSException* exception)
{

//Save exception details

}

int main(int argc, char *argv[])
{
    @autoreleasepool {

        NSSetUncaughtExceptionHandler(&onUncaughtException);

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([SGGI_AppDelegate class]));
    }
}



-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 {
      //Add coding to find if any exception has occurred from saved details if so send it to server or ask user to comment on the issue.
    //Rest of the coding
 }

方法三:

int main(int argc, char *argv[])
{
    @autoreleasepool {

        @try {

            return UIApplicationMain(argc, argv, nil, NSStringFromClass([SGGI_AppDelegate class]));
        }
        @catch (NSException *exception) {      
        //Save the exception
        }
        @finally {
        }

    }
}

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 {
      //Add coding to find if any exception has occurred from saved details if so send it to server or ask user to comment on the issue.
    //Rest of the coding
 }

注意:

  • 在我看来,在崩溃时不要尝试将异常详细信息发送到服务器,而是在他再次启动应用程序时发送。

  • 如果你要使用NSUserDefaults保存异常信息,则必须在崩溃时同步它,否则它不会持久化。

以下代码片段可以完成这项工作。

   - (void)applicationWillTerminate:(UIApplication *)application
   {
       [[NSUserDefaults standardUserDefaults]synchronize];
   }
  • 如果您喜欢将其保存在sqlite数据库中,则它会自行持久化,无需在崩溃时调用任何内容以进行持久化。

这个对任何人都有效吗? 因为对我没用 :(我尝试在 onUncaughtException 方法中保存 nsarray obj / callStackSymbols 到 NSUserDefaults 中。在下一个 didFinishLaunchingWithOptions 中,我无法获取我用于保存在 NSUserDefaults 中的任何键的对象。 - Abhijit
@Abhijit - 在保存后同步([[NSUserDefaults standardUserDefaults]synchronize])它。 - Durai Amuthan.H
虽然我忘了提到这一点,但我当然记得同步Userdefaults。 - Abhijit
1
正是我需要看到的。太好了,Durai。 - Alex Zavatone
重新运行应用程序后,NSUserDefault中的数据返回为nil...我不知道为什么。 - kemdo
很奇怪!你有在 applicationWillTerminate 时同步 NSUserDefaults 吗? - Durai Amuthan.H

5
在XCode中,您应该始终为objc_exception_throw设置全局断点。然后,通常可以获得更有意义的堆栈跟踪,以确定实际上是谁试图引发异常。
即使在没有任何自己代码的情况下,您仍然可能会遇到源于定时器代码或其他地方的异常,但是如果您查看方法链,通常可以大致了解异常的原因(例如发送通知时目标已消失)。

这只有在使用Xcode运行应用程序时才有用吗?而不是在生产应用程序中(例如,将异常写入日志文件)。 - Chris Prince
只有在 XCode 运行应用程序时才有用。 - Kendall Helmstetter Gelner

3

跟踪崩溃报告的另一个选择是开源代码Plausible CrashReporter,它可以自动向您发送来自现场的崩溃报告。

还有另一个开源选项CrashReporterDemo,它是Plausible CrashReporter和一些服务器代码的组合,可以更好地跟踪崩溃报告。

最后,还有MacDevCrashReporter,这个服务似乎与iOSExceptional.com有相似之处,在另一个答案中建议使用。我不知道他们的服务条款是什么,因为我没有注册beta版。在深入了解之前一定要检查。


2

很好KP,但是提及如何实现会更有帮助。 - Alex Zavatone
链接现在已经失效。 - Friend Of George

2
检查一下Crittercism。它在你要求的基础上做得更多,因为它可以让您获取所有使用您的应用程序的用户的信息,所以您应该能够看到自己的崩溃情况。
您还可以上传特定版本的 DYSM 文件,并在其网站上自动对崩溃进行符号化处理。这将为您提供最清晰的堆栈跟踪,而无需连接调试器。
您可能还想确保您设置了 Objective-C 异常断点。在 Xcode 4 中,在断点选项卡中,您可以添加一个异常断点,用于断开 C++ 和 Obj-C 异常。如果没有此功能,大多数异常抛出的堆栈跟踪都很难理解。
祝好运!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接