Jul 27 - Update code for scheduled tasks 4
This commit is contained in:
@@ -10,6 +10,7 @@ import SwiftData
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import UserNotifications
|
||||
import UIKit // beginBackgroundTask — see beginSyncBackgroundTask()
|
||||
|
||||
@MainActor
|
||||
class SyncManager: ObservableObject {
|
||||
@@ -110,6 +111,36 @@ class SyncManager: ObservableObject {
|
||||
pollTask = nil
|
||||
}
|
||||
|
||||
// ── Background task assertion ─────────────────────────────────────────
|
||||
// Keeps the process alive across a suspend so an in-flight sync can finish.
|
||||
// Distinct from the BGProcessingTask in JanitorialQCApp: that one asks iOS
|
||||
// to WAKE us later, this one asks it not to suspend us right now.
|
||||
|
||||
private var syncBackgroundTaskId: UIBackgroundTaskIdentifier = .invalid
|
||||
|
||||
private func beginSyncBackgroundTask() {
|
||||
// triggerSync() is re-entrancy guarded, but assert defensively anyway:
|
||||
// beginning a second assertion would leak the first identifier.
|
||||
guard syncBackgroundTaskId == .invalid else { return }
|
||||
syncBackgroundTaskId = UIApplication.shared.beginBackgroundTask(
|
||||
withName: "JQC.syncDrain"
|
||||
) {
|
||||
// Called on the main thread when the grace period runs out. It MUST
|
||||
// end the assertion or iOS terminates the app. assumeIsolated is
|
||||
// required because the handler is a nonisolated closure under
|
||||
// SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor.
|
||||
MainActor.assumeIsolated {
|
||||
SyncManager.shared.endSyncBackgroundTask()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func endSyncBackgroundTask() {
|
||||
guard syncBackgroundTaskId != .invalid else { return }
|
||||
UIApplication.shared.endBackgroundTask(syncBackgroundTaskId)
|
||||
syncBackgroundTaskId = .invalid
|
||||
}
|
||||
|
||||
/// Called on logout so the next login starts a clean fetch.
|
||||
func resetNotificationPoller() {
|
||||
lastNotificationFetch = nil
|
||||
@@ -232,6 +263,15 @@ class SyncManager: ObservableObject {
|
||||
syncError = nil
|
||||
defer { isSyncing = false }
|
||||
|
||||
// Ask iOS to keep the process alive long enough to finish the drain.
|
||||
// The case this covers: an inspector taps Submit and immediately locks
|
||||
// the iPad or swipes to another app. Without an assertion the process
|
||||
// suspends mid-upload and the work waits for the next launch.
|
||||
// Roughly 30 s of grace; the expiration handler ends it cleanly so iOS
|
||||
// never force-kills us. Harmless in the foreground — it simply ends.
|
||||
beginSyncBackgroundTask()
|
||||
defer { endSyncBackgroundTask() }
|
||||
|
||||
await processPhotoQueue(context: context)
|
||||
await processInspectionQueue(context: context)
|
||||
await processIssueQueue(context: context)
|
||||
|
||||
Reference in New Issue
Block a user