Swift:本地检测应用程序是否崩溃

12

我已经搜索了一圈,但是还没有找到一个不需要指向第三方服务的答案。我只需要在 NSUserDefaults 中保存一个值,这样当下次打开应用程序时,我就可以显示一个警报,说明应用程序已经崩溃了。

谢谢。


请查看:https://dev59.com/FWgv5IYBdhLWcg3wCsg8 - Okhan Okbay
4个回答

25
感谢 @RyanCollins 的帮助,我自己解决了这个问题。在应用程序委托中,applicationWillTerminate 函数仅在应用程序正常关闭时运行。原生检测应用程序崩溃的代码如下所示。 全局定义变量
let crashedNotificationKey = "com.stackoverflow.crashNotificationKey"
var crashedLastTime = true

应用代理

func applicationWillTerminate(application: UIApplication) {
    crashedLastTime = false
    prefs.setBool(crashedLastTime, forKey: "crash")
}

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    crashedLastTime = prefs.boolForKey("crash")
    if crashedLastTime == true {

        crashedLastTime = false
        prefs.setBool(crashedLastTime, forKey: "crash")
        NSNotificationCenter.defaultCenter().postNotificationName(crashedNotificationKey, object: self)

    } else {

        crashedLastTime = true
        prefs.setBool(crashedLastTime, forKey: "crash")

    }

    return true
}

根视图控制器

override func awakeFromNib() {
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "crashedAlert", name: crashedNotificationKey, object: nil)
}

func crashedAlert() {
    let alert = UIAlertController(title: "The app has crashed!", message: "Sorry about that! I am just a 17 year old highschooler making my first game!", preferredStyle: UIAlertControllerStyle.Alert)
    alert.addAction(UIAlertAction(title: "It's cool bro.", style: UIAlertActionStyle.Default, handler: nil))
    self.presentViewController(alert, animated: true, completion: nil)
}

1
尚未测试,但我认为这里有一个错误。在应用程序 didFinishLaunchingWithOptions 函数中检查 crashedLastTime 的值后,始终将存储的布尔值设置为 true。例如:prefs.setBool(true, forKey: "crash")。 - Awesomeness
@Awesomeness 是的!当我们从后台关闭应用程序<向上滑动并关闭>时,将调用applicationWillTerminate - Jack
1
我注意到在iOS 11中,当强制退出应用程序时,applicationWilTerminate方法不会被调用。 - Andrew Bennet
applicationWillResignActive 在用户能够强制退出之前被调用,因此应用程序在用户能够强制退出之前处于非活动状态。我跟踪应用程序状态,并且仅当最后已知状态为活动状态时才将崩溃视为崩溃。当状态为非活动状态或后台状态时,即使实际上崩溃了而不是仅强制退出,它也不会过多地打扰用户。 - Gerd Castan
在这段代码中,我发现了一个错误:如果应用程序崩溃并且我们在didFinishLaunchingWithOptions方法中捕获到了它,在此会话中应用程序再次崩溃,那么由于"crash"键的属性被设置为false,将会错过处理崩溃的步骤。 - Vladimir Prigarin
这段代码错误地假设只有在应用程序崩溃时才不会调用applicationWillTerminate方法。事实并非如此,请参阅applicationWillTerminate的文档。 - HangarRash

6

详情

  • Swift 4.2
  • Xcode 10.1 (10B61)

解决方案1

https://github.com/zixun/CrashEye/blob/master/CrashEye/Classes/CrashEye.swift

解决方案1的完整示例

!!! 不要忘记复制(或安装Pod)解决方案代码 !!!

AppDelegate.swift

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        CrashEye.add(delegate: self)
        return true
    }
}

extension AppDelegate: CrashEyeDelegate {
    func crashEyeDidCatchCrash(with model: CrashModel) {
        UserDefaults.standard.set(model.reason + "(\(Date()))", forKey: "crash")
    }
}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton(frame: CGRect(x: 40, y: 40, width: 80, height: 44))
        button.setTitle("BOOOM", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(boomButtonTouchedUpInside), for: .touchUpInside)
        view.addSubview(button)
    }

    override func viewDidAppear(_ animated: Bool) {
        if let date = UserDefaults.standard.string(forKey: "crash") {
            let alert = UIAlertController(title: "", message: date, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
            present(alert, animated: true)
        }
    }

    @objc func boomButtonTouchedUpInside(_ sender: Any) {
        let arr = [1, 2, 3]
        let elem = arr[4]
    }
}

解决方案2:Crashlytics

安装Crashlytics

解决方案2的完整示例

AppDelegate.swift

import UIKit
import Fabric
import Crashlytics

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        Crashlytics.sharedInstance().delegate = self
        Fabric.with([Crashlytics.self])
        return true
    }
}

extension AppDelegate: CrashlyticsDelegate {
    func crashlyticsDidDetectReport(forLastExecution report: CLSReport) {
        let alert = UIAlertController(title: "\(report.dateCreated)", message: report.identifier, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
        DispatchQueue.global(qos: .background).async {
            sleep(3)
            DispatchQueue.main.async {
                self.window?.rootViewController?.present(alert, animated: true)
            }
        }
    }
}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton(frame: CGRect(x: 40, y: 40, width: 80, height: 44))
        button.setTitle("BOOOM", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(boomButtonTouchedUpInside), for: .touchUpInside)
        view.addSubview(button)
    }

    @objc func boomButtonTouchedUpInside(_ sender: Any) {
        let arr = [1, 2, 3]
        let elem = arr[4]
    }
}

结果

  1. 启动应用程序
  2. 点击“Booom”按钮(应用程序将崩溃)
  3. 再次启动应用程序

    输入图片描述


2
问题是,如果应用程序崩溃了,它就无法运行代码来写入NSUserDefaults。
我知道的最好的解决方案是使用PLCrashReporter(https://plcrashreporter.org)。

有没有一个函数或其他东西,可以在应用程序正常关闭时运行?例如,applicationWillResignActive,但是即使应用程序崩溃,它也会运行。 - zennox
是的,在您的应用程序委托中,有像applicationWillTerminateapplicationDidEnterBackground这样的函数可以做到这一点。 - Ryan Collins
我会调查一下并回复您。 - zennox

1

FirebaseCrashlytics

如果你不使用已被弃用的Fabric,你需要在你的ViewController中使用以下代码,该代码在升级到Firebase Crashlytics SDK中有提到。

我是这样使用的:

// 1. import Crashlytics
import FirebaseCrashlytics

class YOUR_ROOT_VIEW_CONTROLLER {

   override viewDidLoad(){
       //...

       // 2. register to get the notifications
       self.configCrashlytics()

       // ...
   }

    func configCrashlytics() {
        /* You must set setCrashlyticsCollectionEnabled to false in order to use
        checkForUnsentReportsWithCompletion. */

        Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(false)

        Crashlytics.crashlytics().checkForUnsentReports { hasUnsentReport in
          let hasUserConsent = false
          // ...get user consent.

          if hasUserConsent && hasUnsentReport {
            Crashlytics.crashlytics().sendUnsentReports()
          } else {
            Crashlytics.crashlytics().deleteUnsentReports()
          }
        }

        // Detect when a crash happens during your app's last run.
        if Crashlytics.crashlytics().didCrashDuringPreviousExecution() {
          // ...notify the user that last time the app crashed
          
        }
        
    }
    // 4. Add a button that run fatallError()
    @IBAction func crashApp(_ sender: UIButton) {
        fatalError()
    }
}


2
我正在寻找这一行代码:Crashlytics.crashlytics().didCrashDuringPreviousExecution() - Coder ACJHP

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