From 11dc19f7ed436c4e08198bd1e12a28d156686fec Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 30 Sep 2026 15:01:38 +0300 Subject: [PATCH] Await the session restore instead of polling for it Replace the 100ms polling loop in the background app refresh with a subject that the state machine's transition handler sets as soon as the app is no longer restoring a session, whatever the outcome. The refresh now awaits it once, with the same 5 second timeout, instead of waking up repeatedly. Move the wait back into the class body and silence type_body_length for AppCoordinator rather than keeping a single-function extension around to stay under the limit. --- .../Sources/Application/AppCoordinator.swift | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index c4967419e..ff6d7e999 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -15,6 +15,7 @@ import Sentry import SwiftUI import Version +// swiftlint:disable:next type_body_length class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDelegate, NotificationManagerDelegate, SecureWindowManagerDelegate { private let stateMachine: AppCoordinatorStateMachine private let navigationRootCoordinator: NavigationRootCoordinator @@ -60,6 +61,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg private var softLogoutCoordinator: SoftLogoutScreenCoordinator? private var appDelegateObserver: AnyCancellable? private var userSessionObserver: AnyCancellable? + /// Becomes `true` once the app is no longer waiting on a session restore, whatever the outcome. + private let isSessionRestoredSubject = CurrentValueSubject(false) private var clientProxyObserver: AnyCancellable? private var cancellables = Set() @@ -618,6 +621,10 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg stateMachine.addTransitionHandler { [weak self] context in guard let self else { return } + if context.toState != .restoringSession { + isSessionRestoredSubject.send(true) + } + switch (context.fromState, context.event, context.toState) { case (.initial, .startWithAuthentication, .signedOut): startAuthentication() @@ -1336,6 +1343,13 @@ private extension AppCoordinator { } } } + + /// When iOS relaunches a terminated app for a background task, the session is still being restored asynchronously. + private func waitForSessionRestore() async { + for await isSessionRestored in isSessionRestoredSubject.timeout(.seconds(5), scheduler: DispatchQueue.main).values where isSessionRestored { + return + } + } } // MARK: Search backfill @@ -1397,15 +1411,3 @@ private extension AppCoordinator { task.setTaskCompleted(success: true) } } - -private extension AppCoordinator { - /// When iOS relaunches a terminated app for a background task, the session is still being restored asynchronously. - func waitForSessionRestore() async { - let deadline = ContinuousClock.now + .seconds(5) - while userSession == nil, - stateMachine.state == .initial || stateMachine.state == .restoringSession, - ContinuousClock.now < deadline { - try? await Task.sleep(for: .milliseconds(100)) - } - } -}