mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Fix state machine crashes & background tasks (#343)
* Fix `UIApplication.shared` after moving to SwiftUI app * Do not autoplay videos on background * Move app state changes into the app coordinator * Add application background task, move into the suspended state more accurately * Add changelog * Fix most of the linter errors * Strip suspended state from state machine * Fix build * Clear audio session warning * Update AppCoordinator.swift * Update AppCoordinator.swift * Swift format
This commit is contained in:
@@ -22,6 +22,14 @@ class AppCoordinator: AppCoordinatorProtocol {
|
|||||||
private let stateMachine: AppCoordinatorStateMachine
|
private let stateMachine: AppCoordinatorStateMachine
|
||||||
private let navigationController: NavigationController
|
private let navigationController: NavigationController
|
||||||
private let userSessionStore: UserSessionStoreProtocol
|
private let userSessionStore: UserSessionStoreProtocol
|
||||||
|
/// Common background task to resume long-running tasks in the background.
|
||||||
|
/// When this task expiring, we'll try to suspend the state machine by `suspend` event.
|
||||||
|
private var backgroundTask: BackgroundTaskProtocol?
|
||||||
|
private var isSuspended = false {
|
||||||
|
didSet {
|
||||||
|
MXLog.debug("didSet to: \(isSuspended)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var userSession: UserSessionProtocol! {
|
private var userSession: UserSessionProtocol! {
|
||||||
didSet {
|
didSet {
|
||||||
@@ -52,7 +60,9 @@ class AppCoordinator: AppCoordinatorProtocol {
|
|||||||
|
|
||||||
ServiceLocator.shared.register(userNotificationController: UserNotificationController(rootCoordinator: navigationController))
|
ServiceLocator.shared.register(userNotificationController: UserNotificationController(rootCoordinator: navigationController))
|
||||||
|
|
||||||
backgroundTaskService = UIKitBackgroundTaskService(withApplication: UIApplication.shared)
|
backgroundTaskService = UIKitBackgroundTaskService {
|
||||||
|
UIApplication.shared
|
||||||
|
}
|
||||||
|
|
||||||
userSessionStore = UserSessionStore(backgroundTaskService: backgroundTaskService)
|
userSessionStore = UserSessionStore(backgroundTaskService: backgroundTaskService)
|
||||||
|
|
||||||
@@ -61,6 +71,8 @@ class AppCoordinator: AppCoordinatorProtocol {
|
|||||||
setupLogging()
|
setupLogging()
|
||||||
|
|
||||||
Bundle.elementFallbackLanguage = "en"
|
Bundle.elementFallbackLanguage = "en"
|
||||||
|
|
||||||
|
startObservingApplicationState()
|
||||||
|
|
||||||
// Benchmark.trackingEnabled = true
|
// Benchmark.trackingEnabled = true
|
||||||
}
|
}
|
||||||
@@ -324,6 +336,52 @@ class AppCoordinator: AppCoordinatorProtocol {
|
|||||||
private func showLoginErrorToast() {
|
private func showLoginErrorToast() {
|
||||||
ServiceLocator.shared.userNotificationController.submitNotification(UserNotification(title: "Failed logging in"))
|
ServiceLocator.shared.userNotificationController.submitNotification(UserNotification(title: "Failed logging in"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Application State
|
||||||
|
|
||||||
|
private func pause() {
|
||||||
|
userSession?.clientProxy.stopSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resume() {
|
||||||
|
userSession?.clientProxy.startSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startObservingApplicationState() {
|
||||||
|
NotificationCenter.default.addObserver(self,
|
||||||
|
selector: #selector(applicationWillResignActive),
|
||||||
|
name: UIApplication.willResignActiveNotification,
|
||||||
|
object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self,
|
||||||
|
selector: #selector(applicationDidBecomeActive),
|
||||||
|
name: UIApplication.didBecomeActiveNotification,
|
||||||
|
object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
private func applicationWillResignActive() {
|
||||||
|
guard backgroundTask == nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
backgroundTask = backgroundTaskService.startBackgroundTask(withName: "SuspendApp: \(UUID().uuidString)") { [weak self] in
|
||||||
|
self?.pause()
|
||||||
|
|
||||||
|
self?.backgroundTask = nil
|
||||||
|
self?.isSuspended = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
private func applicationDidBecomeActive() {
|
||||||
|
backgroundTask?.stop()
|
||||||
|
backgroundTask = nil
|
||||||
|
|
||||||
|
if isSuspended {
|
||||||
|
isSuspended = false
|
||||||
|
resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - AuthenticationCoordinatorDelegate
|
// MARK: - AuthenticationCoordinatorDelegate
|
||||||
|
|||||||
@@ -62,27 +62,38 @@ class AppCoordinatorStateMachine {
|
|||||||
private let stateMachine: StateMachine<State, Event>
|
private let stateMachine: StateMachine<State, Event>
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
stateMachine = StateMachine(state: .initial) { machine in
|
stateMachine = StateMachine(state: .initial)
|
||||||
machine.addRoutes(event: .startWithAuthentication, transitions: [.initial => .signedOut])
|
configure()
|
||||||
machine.addRoutes(event: .succeededSigningIn, transitions: [.signedOut => .signedIn])
|
}
|
||||||
|
|
||||||
machine.addRoutes(event: .startWithExistingSession, transitions: [.initial => .restoringSession])
|
private func configure() {
|
||||||
machine.addRoutes(event: .succeededRestoringSession, transitions: [.restoringSession => .signedIn])
|
stateMachine.addRoutes(event: .startWithAuthentication, transitions: [.initial => .signedOut])
|
||||||
machine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut])
|
stateMachine.addRoutes(event: .succeededSigningIn, transitions: [.signedOut => .signedIn])
|
||||||
|
|
||||||
machine.addRoutes(event: .signOut, transitions: [.any => .signingOut])
|
stateMachine.addRoutes(event: .startWithExistingSession, transitions: [.initial => .restoringSession])
|
||||||
machine.addRoutes(event: .completedSigningOut, transitions: [.signingOut => .signedOut])
|
stateMachine.addRoutes(event: .succeededRestoringSession, transitions: [.restoringSession => .signedIn])
|
||||||
|
stateMachine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut])
|
||||||
// Transitions with associated values need to be handled through `addRouteMapping`
|
|
||||||
machine.addRouteMapping { event, fromState, _ in
|
stateMachine.addRoutes(event: .signOut, transitions: [.any => .signingOut])
|
||||||
switch (event, fromState) {
|
stateMachine.addRoutes(event: .completedSigningOut, transitions: [.signingOut => .signedOut])
|
||||||
case (.remoteSignOut(let isSoft), _):
|
|
||||||
return .remoteSigningOut(isSoft: isSoft)
|
// Transitions with associated values need to be handled through `addRouteMapping`
|
||||||
case (.completedSigningOut, .remoteSigningOut):
|
stateMachine.addRouteMapping { event, fromState, _ in
|
||||||
return .signedOut
|
switch (event, fromState) {
|
||||||
default:
|
case (.remoteSignOut(let isSoft), _):
|
||||||
return nil
|
return .remoteSigningOut(isSoft: isSoft)
|
||||||
}
|
case (.completedSigningOut, .remoteSigningOut):
|
||||||
|
return .signedOut
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addTransitionHandler { context in
|
||||||
|
if let event = context.event {
|
||||||
|
MXLog.info("Transitioning from `\(context.fromState)` to `\(context.toState)` with event `\(event)`")
|
||||||
|
} else {
|
||||||
|
MXLog.info("Transitioning from \(context.fromState)` to `\(context.toState)`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ struct ActivityCoordinator: CoordinatorProtocol {
|
|||||||
let items: [Any]
|
let items: [Any]
|
||||||
|
|
||||||
func toPresentable() -> AnyView {
|
func toPresentable() -> AnyView {
|
||||||
return AnyView(UIActivityViewControllerWrapper(activityItems: items)
|
AnyView(UIActivityViewControllerWrapper(activityItems: items)
|
||||||
.presentationDetents([.medium])
|
.presentationDetents([.medium])
|
||||||
.ignoresSafeArea())
|
.ignoresSafeArea())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ final class VideoPlayerCoordinator: CoordinatorProtocol {
|
|||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
|
|
||||||
viewModel = VideoPlayerViewModel(videoURL: parameters.videoURL,
|
viewModel = VideoPlayerViewModel(videoURL: parameters.videoURL,
|
||||||
|
autoplay: UIApplication.shared.applicationState == .active,
|
||||||
isModallyPresented: parameters.isModallyPresented)
|
isModallyPresented: parameters.isModallyPresented)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +68,7 @@ final class VideoPlayerCoordinator: CoordinatorProtocol {
|
|||||||
private func configureAudioSession(_ session: AVAudioSession) {
|
private func configureAudioSession(_ session: AVAudioSession) {
|
||||||
do {
|
do {
|
||||||
try session.setCategory(.playback,
|
try session.setCategory(.playback,
|
||||||
mode: .default,
|
mode: .default)
|
||||||
options: [.allowBluetooth, .allowBluetoothA2DP])
|
|
||||||
try session.setActive(true)
|
try session.setActive(true)
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.debug("Configure audio session failed: \(error)")
|
MXLog.debug("Configure audio session failed: \(error)")
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class UIKitBackgroundTask: BackgroundTaskProtocol {
|
|||||||
// attempt to start
|
// attempt to start
|
||||||
identifier = application.beginBackgroundTask(withName: name) { [weak self] in
|
identifier = application.beginBackgroundTask(withName: name) { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
self.stop()
|
||||||
self.expirationHandler?(self)
|
self.expirationHandler?(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,13 +19,17 @@ import UIKit
|
|||||||
|
|
||||||
/// /// UIKitBackgroundTaskService is a concrete implementation of BackgroundTaskServiceProtocol using a given `ApplicationProtocol` instance.
|
/// /// UIKitBackgroundTaskService is a concrete implementation of BackgroundTaskServiceProtocol using a given `ApplicationProtocol` instance.
|
||||||
class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||||
private let application: ApplicationProtocol?
|
private let applicationBlock: () -> ApplicationProtocol?
|
||||||
private var reusableTasks: WeakDictionary<String, UIKitBackgroundTask> = WeakDictionary()
|
private var reusableTasks: WeakDictionary<String, UIKitBackgroundTask> = WeakDictionary()
|
||||||
|
|
||||||
|
private var application: ApplicationProtocol? {
|
||||||
|
applicationBlock()
|
||||||
|
}
|
||||||
|
|
||||||
/// Initializer
|
/// Initializer
|
||||||
/// - Parameter application: application instance to use. Defaults to `UIApplication.extensionSafeShared`.
|
/// - Parameter applicationBlock: block returning the application instance to use. Defaults to a block returning `UIApplication.extensionSafeShared`.
|
||||||
init(withApplication application: ApplicationProtocol? = UIApplication.extensionSafeShared) {
|
init(withApplicationBlock applicationBlock: @escaping () -> ApplicationProtocol? = { UIApplication.extensionSafeShared }) {
|
||||||
self.application = application
|
self.applicationBlock = applicationBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
func startBackgroundTask(withName name: String,
|
func startBackgroundTask(withName name: String,
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
|||||||
self.bugReportService = bugReportService
|
self.bugReportService = bugReportService
|
||||||
|
|
||||||
setupStateMachine()
|
setupStateMachine()
|
||||||
startObservingApplicationState()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
@@ -84,11 +83,6 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
|||||||
case (.feedbackScreen, .dismissedFeedbackScreen, .homeScreen):
|
case (.feedbackScreen, .dismissedFeedbackScreen, .homeScreen):
|
||||||
break
|
break
|
||||||
|
|
||||||
case (_, .resignActive, .suspended):
|
|
||||||
self.pause()
|
|
||||||
case (_, .becomeActive, _):
|
|
||||||
self.resume()
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fatalError("Unknown transition: \(context)")
|
fatalError("Unknown transition: \(context)")
|
||||||
}
|
}
|
||||||
@@ -98,17 +92,6 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
|||||||
fatalError("Failed transition with context: \(context)")
|
fatalError("Failed transition with context: \(context)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startObservingApplicationState() {
|
|
||||||
NotificationCenter.default.addObserver(self,
|
|
||||||
selector: #selector(applicationWillResignActive),
|
|
||||||
name: UIApplication.willResignActiveNotification,
|
|
||||||
object: nil)
|
|
||||||
NotificationCenter.default.addObserver(self,
|
|
||||||
selector: #selector(applicationDidBecomeActive),
|
|
||||||
name: UIApplication.didBecomeActiveNotification,
|
|
||||||
object: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func presentHomeScreen() {
|
private func presentHomeScreen() {
|
||||||
userSession.clientProxy.startSync()
|
userSession.clientProxy.startSync()
|
||||||
@@ -247,24 +230,4 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
|||||||
self?.stateMachine.processEvent(.dismissedFeedbackScreen)
|
self?.stateMachine.processEvent(.dismissedFeedbackScreen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Application State
|
|
||||||
|
|
||||||
private func pause() {
|
|
||||||
userSession.clientProxy.stopSync()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func resume() {
|
|
||||||
userSession.clientProxy.startSync()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc
|
|
||||||
private func applicationWillResignActive() {
|
|
||||||
stateMachine.processEvent(.resignActive)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc
|
|
||||||
private func applicationDidBecomeActive() {
|
|
||||||
stateMachine.processEvent(.becomeActive)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,6 @@ class UserSessionFlowCoordinatorStateMachine {
|
|||||||
|
|
||||||
/// Showing the settings screen
|
/// Showing the settings screen
|
||||||
case settingsScreen
|
case settingsScreen
|
||||||
|
|
||||||
/// Application has been suspended
|
|
||||||
case suspended
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Events that can be triggered on the AppCoordinator state machine
|
/// Events that can be triggered on the AppCoordinator state machine
|
||||||
@@ -68,22 +65,15 @@ class UserSessionFlowCoordinatorStateMachine {
|
|||||||
case showSessionVerificationScreen
|
case showSessionVerificationScreen
|
||||||
/// Session verification has finished
|
/// Session verification has finished
|
||||||
case dismissedSessionVerificationScreen
|
case dismissedSessionVerificationScreen
|
||||||
|
|
||||||
/// Application goes into inactive state
|
|
||||||
case resignActive
|
|
||||||
/// Application goes into active state
|
|
||||||
case becomeActive
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private let stateMachine: StateMachine<State, Event>
|
private let stateMachine: StateMachine<State, Event>
|
||||||
private var stateBeforeSuspension: State?
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
stateMachine = StateMachine(state: .initial)
|
stateMachine = StateMachine(state: .initial)
|
||||||
configure()
|
configure()
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next cyclomatic_complexity
|
|
||||||
private func configure() {
|
private func configure() {
|
||||||
stateMachine.addRoutes(event: .start, transitions: [.initial => .homeScreen])
|
stateMachine.addRoutes(event: .start, transitions: [.initial => .homeScreen])
|
||||||
|
|
||||||
@@ -109,18 +99,6 @@ class UserSessionFlowCoordinatorStateMachine {
|
|||||||
case (.dismissedSessionVerificationScreen, .sessionVerificationScreen):
|
case (.dismissedSessionVerificationScreen, .sessionVerificationScreen):
|
||||||
return .homeScreen
|
return .homeScreen
|
||||||
|
|
||||||
case (.resignActive, _):
|
|
||||||
self.stateBeforeSuspension = fromState
|
|
||||||
return .suspended
|
|
||||||
case (.becomeActive, _):
|
|
||||||
// Cannot become active if not previously suspended
|
|
||||||
// Happens when the app is backgrounded before the session is setup
|
|
||||||
guard let previousState = self.stateBeforeSuspension else {
|
|
||||||
return self.stateMachine.state
|
|
||||||
}
|
|
||||||
|
|
||||||
return previousState
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,14 +25,18 @@ class BackgroundTaskTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testInAnExtension() {
|
func testInAnExtension() {
|
||||||
let service = UIKitBackgroundTaskService(withApplication: nil)
|
let service = UIKitBackgroundTaskService {
|
||||||
|
nil
|
||||||
|
}
|
||||||
let task = service.startBackgroundTask(withName: Constants.bgTaskName)
|
let task = service.startBackgroundTask(withName: Constants.bgTaskName)
|
||||||
|
|
||||||
XCTAssertNil(task, "Task should not be created")
|
XCTAssertNil(task, "Task should not be created")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInitAndStop() {
|
func testInitAndStop() {
|
||||||
let service = UIKitBackgroundTaskService(withApplication: UIApplication.mockHealty)
|
let service = UIKitBackgroundTaskService {
|
||||||
|
UIApplication.mockHealty
|
||||||
|
}
|
||||||
guard let task = service.startBackgroundTask(withName: Constants.bgTaskName) else {
|
guard let task = service.startBackgroundTask(withName: Constants.bgTaskName) else {
|
||||||
XCTFail("Failed to setup test conditions")
|
XCTFail("Failed to setup test conditions")
|
||||||
return
|
return
|
||||||
@@ -48,7 +52,9 @@ class BackgroundTaskTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testNotReusableInit() {
|
func testNotReusableInit() {
|
||||||
let service = UIKitBackgroundTaskService(withApplication: UIApplication.mockHealty)
|
let service = UIKitBackgroundTaskService {
|
||||||
|
UIApplication.mockHealty
|
||||||
|
}
|
||||||
|
|
||||||
// create two not reusable task with the same name
|
// create two not reusable task with the same name
|
||||||
guard let task1 = service.startBackgroundTask(withName: Constants.bgTaskName),
|
guard let task1 = service.startBackgroundTask(withName: Constants.bgTaskName),
|
||||||
@@ -63,7 +69,9 @@ class BackgroundTaskTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testReusableInit() {
|
func testReusableInit() {
|
||||||
let service = UIKitBackgroundTaskService(withApplication: UIApplication.mockHealty)
|
let service = UIKitBackgroundTaskService {
|
||||||
|
UIApplication.mockHealty
|
||||||
|
}
|
||||||
|
|
||||||
// create two reusable task with the same name
|
// create two reusable task with the same name
|
||||||
guard let task1 = service.startBackgroundTask(withName: Constants.bgTaskName, isReusable: true),
|
guard let task1 = service.startBackgroundTask(withName: Constants.bgTaskName, isReusable: true),
|
||||||
@@ -82,7 +90,9 @@ class BackgroundTaskTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testMultipleStops() {
|
func testMultipleStops() {
|
||||||
let service = UIKitBackgroundTaskService(withApplication: UIApplication.mockHealty)
|
let service = UIKitBackgroundTaskService {
|
||||||
|
UIApplication.mockHealty
|
||||||
|
}
|
||||||
|
|
||||||
// create two reusable task with the same name
|
// create two reusable task with the same name
|
||||||
guard let task = service.startBackgroundTask(withName: Constants.bgTaskName, isReusable: true),
|
guard let task = service.startBackgroundTask(withName: Constants.bgTaskName, isReusable: true),
|
||||||
@@ -103,7 +113,9 @@ class BackgroundTaskTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testNotValidReuse() {
|
func testNotValidReuse() {
|
||||||
let service = UIKitBackgroundTaskService(withApplication: UIApplication.mockHealty)
|
let service = UIKitBackgroundTaskService {
|
||||||
|
UIApplication.mockHealty
|
||||||
|
}
|
||||||
|
|
||||||
// create two reusable task with the same name
|
// create two reusable task with the same name
|
||||||
guard let task = service.startBackgroundTask(withName: Constants.bgTaskName, isReusable: true) else {
|
guard let task = service.startBackgroundTask(withName: Constants.bgTaskName, isReusable: true) else {
|
||||||
@@ -123,7 +135,9 @@ class BackgroundTaskTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testValidReuse() {
|
func testValidReuse() {
|
||||||
let service = UIKitBackgroundTaskService(withApplication: UIApplication.mockHealty)
|
let service = UIKitBackgroundTaskService {
|
||||||
|
UIApplication.mockHealty
|
||||||
|
}
|
||||||
|
|
||||||
// create two reusable task with the same name
|
// create two reusable task with the same name
|
||||||
guard let task = service.startBackgroundTask(withName: Constants.bgTaskName, isReusable: true) else {
|
guard let task = service.startBackgroundTask(withName: Constants.bgTaskName, isReusable: true) else {
|
||||||
@@ -147,7 +161,9 @@ class BackgroundTaskTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testBrokenApp() {
|
func testBrokenApp() {
|
||||||
let service = UIKitBackgroundTaskService(withApplication: UIApplication.mockBroken)
|
let service = UIKitBackgroundTaskService {
|
||||||
|
UIApplication.mockBroken
|
||||||
|
}
|
||||||
|
|
||||||
// create two reusable task with the same name
|
// create two reusable task with the same name
|
||||||
let task = service.startBackgroundTask(withName: Constants.bgTaskName)
|
let task = service.startBackgroundTask(withName: Constants.bgTaskName)
|
||||||
@@ -156,7 +172,9 @@ class BackgroundTaskTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testNoTimeApp() {
|
func testNoTimeApp() {
|
||||||
let service = UIKitBackgroundTaskService(withApplication: UIApplication.mockAboutToSuspend)
|
let service = UIKitBackgroundTaskService {
|
||||||
|
UIApplication.mockAboutToSuspend
|
||||||
|
}
|
||||||
|
|
||||||
// create two reusable task with the same name
|
// create two reusable task with the same name
|
||||||
let task = service.startBackgroundTask(withName: Constants.bgTaskName)
|
let task = service.startBackgroundTask(withName: Constants.bgTaskName)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Application: Fix background tasks & state machine crashes.
|
||||||
Reference in New Issue
Block a user