ios - 仅在最近没有崩溃时才显示 SKStoreReviewController?

标签 ios crash crashlytics

仅当此特定用户/应用安装没有发生应用崩溃(最近)时,我才会调用 SKStoreReviewController.requestReview()

所以问题是,我如何在应用程序中知道该应用程序本身至少崩溃过一次或最近崩溃过?

我正在使用 Crashlytics。因此应用程序可以询问 Crashlytics 我是否知道如何操作。但如果没有 Crashlytics 的答案也将非常受欢迎。

最佳答案

我根据我在评论中共享的链接组合了一个类来处理此问题。这将跟踪“崩溃”的逻辑分离到一个可重用的类中,该类可以放入任何项目中。

一些注意事项:

  • 这不会捕获后台发生的崩溃。
  • 这只是在应用程序启动时将 UserDefaults 中的 bool 值设置为“true”,而在应用程序正常关闭时将其设置为“false”。我觉得这有点老套,但它应该可以完成你想做的事情。
  • 我将应用程序崩溃检查放在 ViewController 代码中。您可能需要检查 applicationDidFinishLaunching 中的崩溃情况,以防应用程序在 View Controller 加载之前崩溃。检查 appDidCrash() 会重置 UserDefaults 中的跟踪器。

话虽这么说,它会捕获用户在使用应用程序时看到的崩溃。

import Foundation

class CrashTracker {

    // variable to hold the key used to store the crash record in UserDefaults
    static let defaultsKey = "com.YOUR_BUNDLE_ID.crashRecord"

    init() {
        registerListeners()
    }

    // sets up listeners for the app entering the background or terminating
    func registerListeners() {
        NotificationCenter.default.addObserver(self, selector: #selector(enteredBackground), name: .UIApplicationDidEnterBackground
        , object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(willTerminate), name: .UIApplicationWillTerminate, object: nil)
    }

    @objc func enteredBackground() {
        // app didn't crash, user closed the app so update UserDefaults
        UserDefaults.standard.set(false, forKey: CrashTracker.defaultsKey)
    }

    @objc func willTerminate() {
        // app didn't crash, user closed the app so update UserDefaults
        UserDefaults.standard.set(false, forKey: CrashTracker.defaultsKey)
    }

    static func appDidCrash() -> Bool {
        // get the current value
        let storedValue = UserDefaults.standard.bool(forKey: CrashTracker.defaultsKey)
        // reset to true to track current launch
        UserDefaults.standard.set(true, forKey: CrashTracker.defaultsKey)
        return storedValue
    }

}

在应用程序委托(delegate)中进行设置:

class AppDelegate: UIResponder, UIApplicationDelegate {

    var crashTracker = CrashTracker()

...

然后在您的 View Controller 中(或者您想要显示警报的任何地方,也许应用程序启动后...applicationDidFinishLaunching)

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if CrashTracker.appDidCrash() {
        print("caught crash ---------")
    } else {
        print("No crash... all good")
        SKStoreReviewController.requestReview()
    }
}

我尝试了 DispatchSourceSignal,因为它似乎是一种更好/更可靠的跟踪崩溃的方法,但永远无法让它与 Swift 4 一起工作。我永远无法获得任何事件处理程序开火。

这里有一个小示例实现供引用。

这对我不起作用 为了完整性,我将其放在这里,以防其他人想进一步尝试。

class TrackCrashViaDispatch {

    var sigtrap: DispatchSourceSignal?
    var sigint: DispatchSourceSignal?
    var sigabrt: DispatchSourceSignal?
    var sigill: DispatchSourceSignal?
    var sigseg: DispatchSourceSignal?
    var sigfpe: DispatchSourceSignal?
    var sigbus: DispatchSourceSignal?
    var sigpipe: DispatchSourceSignal?


    init() {

        // handle obj-c exceptions
        NSSetUncaughtExceptionHandler { exception in
            TrackCrashViaDispatch.registerCrash()
        }

        setupHandlers()
    }

    func setupHandlers() {

        print("Setting up handlers...")

        signal(SIGTRAP, SIG_IGN) // // Make sure the signal does not terminate the application.
        sigtrap = DispatchSource.makeSignalSource(signal: SIGTRAP, queue: .main)
        sigtrap?.setEventHandler {
            print("Got SIGTRAP")
            TrackCrashViaDispatch.registerCrash()
            exit(0)
        }
        sigtrap?.resume()


        signal(SIGINT, SIG_IGN) // Make sure the signal does not terminate the application.
        sigint = DispatchSource.makeSignalSource(signal: SIGINT, queue: .main)
        sigint?.setEventHandler {
            print("Got SIGINT")
            TrackCrashViaDispatch.registerCrash()
            exit(0)
        }
        sigint?.resume()

        signal(SIGABRT, SIG_IGN)
        sigabrt = DispatchSource.makeSignalSource(signal: SIGABRT, queue: .main)
        sigabrt?.setEventHandler {
            print("Got SIGABRT")
            TrackCrashViaDispatch.registerCrash()
            exit(0)
        }
        sigabrt?.resume()

        signal(SIGILL, SIG_IGN)
        sigill = DispatchSource.makeSignalSource(signal: SIGILL, queue: .main)
        sigill?.setEventHandler {
            print("Got SIGILL")
            TrackCrashViaDispatch.registerCrash()
            exit(0)
        }
        sigill?.resume()

        signal(SIGSEGV, SIG_IGN)
        sigseg = DispatchSource.makeSignalSource(signal: SIGSEGV, queue: .main)
        sigseg?.setEventHandler {
            print("Got SIGSEGV")
            TrackCrashViaDispatch.registerCrash()
            exit(0)
        }
        sigseg?.resume()

        signal(SIGFPE, SIG_IGN)
        sigfpe = DispatchSource.makeSignalSource(signal: SIGFPE, queue: .main)
        sigfpe?.setEventHandler {
            print("Got SIGFPE")
            TrackCrashViaDispatch.registerCrash()
            exit(0)
        }
        sigfpe?.resume()

        signal(SIGBUS, SIG_IGN)
        sigbus = DispatchSource.makeSignalSource(signal: SIGBUS, queue: .main)
        sigbus?.setEventHandler {
            print("Got SIGBUS")
            TrackCrashViaDispatch.registerCrash()
            exit(0)
        }
        sigbus?.resume()

        signal(SIGPIPE, SIG_IGN)
        sigpipe = DispatchSource.makeSignalSource(signal: SIGPIPE, queue: .main)
        sigpipe?.setEventHandler {
            print("Got SIGPIPE")
            TrackCrashViaDispatch.registerCrash()
            exit(0)
        }
        sigpipe?.resume()
    }

    static func registerCrash() {
        print("Registering crash")
        UserDefaults.standard.set(true, forKey: "com.YOUR_BUNDLE_ID.crashRecord")
    }

    static func appDidCrash() -> Bool {
        let defaults = UserDefaults.standard
        // get the current value
        let storedValue = defaults.value(forKey: "com.YOUR_BUNDLE_ID.crashRecord")
        // set to nil to track next time
        defaults.set(nil, forKey: "com.YOUR_BUNDLE_ID.crashRecord")

        return storedValue != nil
    }
}

我尝试了第二种解决方案,对处理程序使用不同的队列,删除每个处理程序的忽略调用signal(SIGILL, SIG_IGN)并使变量全局化。要么我对 DispatchSourceSignal 的理解不够深入,要么这种方法不起作用。

关于ios - 仅在最近没有崩溃时才显示 SKStoreReviewController?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52193652/

相关文章:

ios - 如何使用 iOS 在 Storyboard 上创建 ImageView 中心?

java - ClassNotFoundException 和应用程序崩溃

android - RecyclerView在Android 6上崩溃

android - 使用 Jar 转换类,合并调试重复条目

ios - 我将部署目标设置为 iOS8 ,应用商店显示 "Compatibility: Requires iOS 7.0 or later"。我可以强制应用程序仅安装在 iOS8 中吗?

ios - 两个 View Controller 的 TouchesMoved 方法

android-studio - Android 工作室崩溃

android - Crashlytics 未显示发布版本 Android 的崩溃

ios - 是否可以从使用企业许可证分发的应用程序中获取崩溃日志?

objective-c - 连接到多个 Controller 的 iOS subview 小部件