Improve concurrency model for iOS

This commit is contained in:
2026-01-08 19:18:44 -07:00
parent 1c47dd93b0
commit ec63d7c58f
15 changed files with 137 additions and 205 deletions

View File

@@ -10,7 +10,7 @@ class SyncService: ObservableObject {
@Published var isConnected = false
@Published var isTesting = false
@Published var isOfflineMode = false
@Published var providerType: SyncProviderType = .server {
didSet {
updateActiveProvider()
@@ -23,8 +23,6 @@ class SyncService: ObservableObject {
private let userDefaults = UserDefaults.standard
private let logTag = "SyncService"
private var syncTask: Task<Void, Never>?
private var pendingChanges = false
private let syncDebounceDelay: TimeInterval = 2.0
private enum Keys {
static let serverURL = "sync_server_url"
@@ -39,7 +37,7 @@ class SyncService: ObservableObject {
// Legacy properties for compatibility with SettingsView
var serverURL: String {
get { userDefaults.string(forKey: Keys.serverURL) ?? "" }
set {
set {
userDefaults.set(newValue, forKey: Keys.serverURL)
// If active provider is server, it will pick up the change from UserDefaults
}
@@ -66,28 +64,28 @@ class SyncService: ObservableObject {
isConnected = userDefaults.bool(forKey: Keys.isConnected)
isAutoSyncEnabled = userDefaults.object(forKey: Keys.autoSyncEnabled) as? Bool ?? true
isOfflineMode = userDefaults.bool(forKey: Keys.offlineMode)
if let savedType = userDefaults.string(forKey: Keys.providerType),
let type = SyncProviderType(rawValue: savedType) {
self.providerType = type
} else {
self.providerType = .server // Default
}
updateActiveProvider()
}
private func updateActiveProvider() {
switch providerType {
case .server:
activeProvider = ServerSyncProvider()
case .iCloud:
// Placeholder for iCloud provider
activeProvider = nil
activeProvider = nil
case .none:
activeProvider = nil
}
// Update status based on new provider
if let provider = activeProvider {
isConnected = provider.isConnected
@@ -101,7 +99,7 @@ class SyncService: ObservableObject {
AppLogger.info("Sync skipped: Offline mode is enabled.", tag: logTag)
return
}
guard let provider = activeProvider else {
if providerType == .none {
return
@@ -127,7 +125,7 @@ class SyncService: ObservableObject {
do {
try await provider.sync(dataManager: dataManager)
// Update last sync time
// Provider might have updated it in UserDefaults, reload it
if let lastSync = userDefaults.object(forKey: Keys.lastSyncTime) as? Date {
@@ -144,12 +142,12 @@ class SyncService: ObservableObject {
AppLogger.error("Test connection failed: No active provider", tag: logTag)
throw SyncError.notConfigured
}
isTesting = true
defer { isTesting = false }
try await provider.testConnection()
isConnected = provider.isConnected
userDefaults.set(isConnected, forKey: Keys.isConnected)
}
@@ -162,34 +160,19 @@ class SyncService: ObservableObject {
return
}
if isSyncing {
pendingChanges = true
return
}
guard !isSyncing else { return }
syncTask?.cancel()
syncTask = Task {
try? await Task.sleep(nanoseconds: UInt64(syncDebounceDelay * 1_000_000_000))
try? await Task.sleep(for: .seconds(2))
guard !Task.isCancelled else { return }
repeat {
pendingChanges = false
do {
try await syncWithServer(dataManager: dataManager)
} catch {
await MainActor.run {
self.isSyncing = false
}
return
}
if pendingChanges {
try? await Task.sleep(nanoseconds: UInt64(syncDebounceDelay * 1_000_000_000))
}
} while pendingChanges && !Task.isCancelled
do {
try await syncWithServer(dataManager: dataManager)
} catch {
self.isSyncing = false
}
}
}
@@ -198,30 +181,26 @@ class SyncService: ObservableObject {
syncTask?.cancel()
syncTask = nil
pendingChanges = false
Task {
do {
try await syncWithServer(dataManager: dataManager)
} catch {
await MainActor.run {
self.isSyncing = false
}
self.isSyncing = false
}
}
}
func disconnect() {
activeProvider?.disconnect()
syncTask?.cancel()
syncTask = nil
pendingChanges = false
isSyncing = false
isConnected = false
lastSyncTime = nil
syncError = nil
// These are shared keys, so clearing them affects all providers if they use them
// But disconnect() is usually user initiated action
userDefaults.set(false, forKey: Keys.isConnected)
@@ -239,8 +218,7 @@ class SyncService: ObservableObject {
userDefaults.removeObject(forKey: Keys.autoSyncEnabled)
syncTask?.cancel()
syncTask = nil
pendingChanges = false
activeProvider?.disconnect()
}