feat(sync): add background task system for nightly CloudKit sync
Add BGTaskScheduler-based background sync for keeping local data fresh: - BackgroundSyncManager: New singleton managing background tasks - BGAppRefreshTask for periodic CloudKit sync (system-determined frequency) - BGProcessingTask for overnight sync + database cleanup (2 AM) - Auto-archives games older than 1 year during cleanup - Info.plist: Added BGTaskSchedulerPermittedIdentifiers - com.sportstime.app.refresh (periodic sync) - com.sportstime.app.db-cleanup (overnight processing) - SportsTimeApp: Integrated background task lifecycle - Register tasks in init() (required before app finishes launching) - Schedule tasks after bootstrap and when app enters background Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import BackgroundTasks
|
||||
|
||||
@main
|
||||
struct SportsTimeApp: App {
|
||||
@@ -14,6 +15,10 @@ struct SportsTimeApp: App {
|
||||
private var transactionListener: Task<Void, Never>?
|
||||
|
||||
init() {
|
||||
// Register background tasks BEFORE app finishes launching
|
||||
// This must happen synchronously in init or applicationDidFinishLaunching
|
||||
BackgroundSyncManager.shared.registerTasks()
|
||||
|
||||
// Start listening for transactions immediately
|
||||
transactionListener = StoreManager.shared.listenForTransactions()
|
||||
}
|
||||
@@ -105,11 +110,19 @@ struct BootstrappedContentView: View {
|
||||
await performBootstrap()
|
||||
}
|
||||
.onChange(of: scenePhase) { _, newPhase in
|
||||
// Sync when app comes to foreground (but not on initial launch)
|
||||
if newPhase == .active && hasCompletedInitialSync {
|
||||
Task {
|
||||
await performBackgroundSync(context: modelContainer.mainContext)
|
||||
switch newPhase {
|
||||
case .active:
|
||||
// Sync when app comes to foreground (but not on initial launch)
|
||||
if hasCompletedInitialSync {
|
||||
Task {
|
||||
await performBackgroundSync(context: modelContainer.mainContext)
|
||||
}
|
||||
}
|
||||
case .background:
|
||||
// Schedule background tasks when app goes to background
|
||||
BackgroundSyncManager.shared.scheduleAllTasks()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,17 +142,23 @@ struct BootstrappedContentView: View {
|
||||
// 2. Configure DataProvider with SwiftData context
|
||||
AppDataProvider.shared.configure(with: context)
|
||||
|
||||
// 3. Load data from SwiftData into memory
|
||||
// 3. Configure BackgroundSyncManager with model container
|
||||
BackgroundSyncManager.shared.configure(with: modelContainer)
|
||||
|
||||
// 4. Load data from SwiftData into memory
|
||||
await AppDataProvider.shared.loadInitialData()
|
||||
|
||||
// 4. Load store products and entitlements
|
||||
// 5. Load store products and entitlements
|
||||
await StoreManager.shared.loadProducts()
|
||||
await StoreManager.shared.updateEntitlements()
|
||||
|
||||
// 5. App is now usable
|
||||
// 6. App is now usable
|
||||
isBootstrapping = false
|
||||
|
||||
// 6. Background: Try to refresh from CloudKit (non-blocking)
|
||||
// 7. Schedule background tasks for future syncs
|
||||
BackgroundSyncManager.shared.scheduleAllTasks()
|
||||
|
||||
// 8. Background: Try to refresh from CloudKit (non-blocking)
|
||||
Task.detached(priority: .background) {
|
||||
await self.performBackgroundSync(context: context)
|
||||
await MainActor.run {
|
||||
|
||||
Reference in New Issue
Block a user