Я собрал класс для обработки этого на основе ссылки, которой я поделился в комментарии. Это разделяет логику, которая отслеживает сбои, в класс многоразового использования, который можно поместить в любой проект.
Несколько заметок:
- Это не поймает сбои, которые происходят в фоновом режиме.
- Это просто устанавливает bool в UserDefaults на «true», когда приложение запускается, и устанавливается на «false», когда приложение закрывается нормально. Мне это кажется хакерским, но оно должно выполнять то, что ты хочешь сделать.
- Я поставил проверку на сбой приложения в коде 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)
для каждого обработчика и сделав переменные глобальными. Либо я недостаточно хорошо понимаю DispatchSourceSignal
, либо этот подход просто не работает.