How to handle crashes during startup on iOS and OS X

Introduction

To catch and send crashes that occur while the app is starting up, the app has to get adjusted a little bit to make this work.

The challenges in this scenario are:

  • Sending crash reports needs to be asynchronous, otherwise it would block the main thread or bad network conditions could make it even worse
  • If the startup takes too long or the main thread is blocking too long, the watchdog process will kill the app
  • The app might crash again before the crash report could have been send

Important note: If you want to test this, keep in mind to let the app crash without the Xcode debugger being attached, because otherwise it will catch all the crashes and the SDK has to chance to detect them.

HowTo

  1. Setup the SDK
  2. Check if the app crashed in the last session by checking [BITCrashManager didCrashInLastSession]
  3. Check if [BITCrashManager timeintervalCrashInLastSessionOccured] is below a treshhold that you define. E.g. say your app usually requires 2 seconds for startup, and giving sending a crash report some time, you mighe choose 5 seconds as the treshhold
  4. If the crash happened in that timeframe, delay your app initialization and show an intermediate screen
  5. Implement the BITCrashManagerDelegate protocol methods - (void)crashManagerWillCancelSendingCrashReport:(BITCrashManager *)crashManager, - (void)crashManager:(BITCrashManager *)crashManager didFailWithError:(NSError *)error; and - (void)crashManagerDidFinishSendingCrashReport:(BITCrashManager *)crashManager; and continue app initialization

Example

Objective-C

    @interface BITAppDelegate () <BITCrashManagerDelegate> {}
    @end


    @implementation BITAppDelegate

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
      [self.window makeKeyAndVisible];

      [[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"<>"
                                                             delegate:self];

      // optionally enable logging to get more information about states.
      [BITHockeyManager sharedHockeyManager].debugLogEnabled = YES;

      [[BITHockeyManager sharedHockeyManager] startManager];

      if ([self didCrashInLastSessionOnStartup]) {
        // show intermediate UI
      } else {
        [self setupApplication];
      }

      return YES;
    }

    - (BOOL)didCrashInLastSessionOnStartup {
      return ([[BITHockeyManager sharedHockeyManager].crashManager didCrashInLastSession] &&
        [[BITHockeyManager sharedHockeyManager].crashManager timeintervalCrashInLastSessionOccured] < 5);
    }

    - (void)setupApplication {
      // setup your app specific code
    }

    #pragma mark - BITCrashManagerDelegate

    - (void)crashManagerWillCancelSendingCrashReport:(BITCrashManager *)crashManager {
      if ([self didCrashInLastSessionOnStartup]) {
        [self setupApplication];
      }
    }

    - (void)crashManager:(BITCrashManager *)crashManager didFailWithError:(NSError *)error {
      if ([self didCrashInLastSessionOnStartup]) {
        [self setupApplication];
      }
    }

    - (void)crashManagerDidFinishSendingCrashReport:(BITCrashManager *)crashManager {
      if ([self didCrashInLastSessionOnStartup]) {
        [self setupApplication];
      }
    }

    @end

Swift

  @UIApplicationMain
  class AppDelegate: UIResponder, UIApplicationDelegate, BITHockeyManagerDelegate, BITCrashManagerDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
      window?.makeKeyAndVisible()

      BITHockeyManager.shared().configure(withIdentifier: "APP_IDENTIFIER", delegate: self)
      BITHockeyManager.shared().logLevel = BITLogLevel.verbose

      if (didCrashInLastSessionOnStartup()) {
        // show intermediate UI
      } else {
        setupApplication()
      }

      return true
    }

    func didCrashInLastSessionOnStartup() -> Bool {
      return (BITHockeyManager.shared().crashManager.didCrashInLastSession &&
        BITHockeyManager.shared().crashManager.timeIntervalCrashInLastSessionOccurred < 5);
    }

    func setupApplication() {
      // setup your app specific code
    }

    func crashManagerWillCancelSendingCrashReport(_ crashManager: BITCrashManager!) {
      if (didCrashInLastSessionOnStartup()) {
        setupApplication()
      }
    }

    func crashManager(_ crashManager: BITCrashManager!, didFailWithError error: Error!) {
      if (didCrashInLastSessionOnStartup()) {
        setupApplication()
      }
    }

    func crashManagerDidFinishSendingCrashReport(_ crashManager: BITCrashManager!) {
      if (didCrashInLastSessionOnStartup()) {
        setupApplication()
      }
    }

  }