首页 > 解决方案 > 仅在最近没有崩溃时才显示 SKStoreReviewController?

问题描述

SKStoreReviewController.requestReview()只想在这个特定的用户/应用程序安装没有应用程序崩溃(最近)时打电话。

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

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

标签: ioscrashcrashlytics

解决方案


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

几点注意事项:

  • 这不会捕获在后台发生的崩溃。
  • 这只是在应用程序启动时将 UserDefaults 中的布尔值设置为“true”,当应用程序正常关闭时设置为“false”。对我来说感觉有点 hacky,但它应该完成你想做的事情。
  • 我在 ViewController 代码中检查应用程序崩溃。applicationDidFinishLaunching如果应用程序在视图控制器加载之前崩溃,您可能需要检查崩溃情况。检查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
    }

}

在应用程序委托中设置它:

class AppDelegate: UIResponder, UIApplicationDelegate {

    var crashTracker = CrashTracker()

...

然后在您的视图控制器中(或任何您想显示警报的地方,可能在应用程序启动后立即... 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)每个处理程序的忽略调用并使 vars 全局。要么我不够了解DispatchSourceSignal,要么这种方法不起作用。


推荐阅读