【问题标题】:Show SKStoreReviewController only when there was no crash lately?仅在最近没有崩溃时才显示 SKStoreReviewController?
【发布时间】:2018-09-05 21:31:40
【问题描述】:

仅当此特定用户/应用安装没有出现应用崩溃(最近)时,我才想致电 SKStoreReviewController.requestReview()。

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

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

【问题讨论】:

标签: ios crash crashlytics


【解决方案1】:

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

几点说明:

  • 这不会捕获后台发生的崩溃。
  • 这只是在应用启动时将 UserDefaults 中的布尔值设置为“真”,而在应用正常关闭时将其设置为“假”。这对我来说有点老套,但它应该可以完成你想做的事情。
  • 我将应用程序崩溃检查放在 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,要么这种方法不起作用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-25
    • 1970-01-01
    • 2015-11-15
    • 2015-04-07
    • 1970-01-01
    相关资源
    最近更新 更多