Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
bd09c14a02
commit
db06b43364
@ -83,21 +83,10 @@ final class AppDataStore: SwiftDataCloudKitStore {
|
|||||||
init(modelContext: ModelContext) {
|
init(modelContext: ModelContext) {
|
||||||
self.modelContainer = modelContext.container
|
self.modelContainer = modelContext.container
|
||||||
self.modelContext = modelContext
|
self.modelContext = modelContext
|
||||||
startSyncObservation()
|
startObservingCloudKitRemoteChanges()
|
||||||
reloadData()
|
reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startSyncObservation() {
|
|
||||||
cloudKitSyncManager.startObserving { [weak self] in
|
|
||||||
guard let self else { return }
|
|
||||||
_ = self.cloudKitSyncManager.processObservedRemoteChange(
|
|
||||||
modelContext: &self.modelContext,
|
|
||||||
modelContainer: self.modelContainer
|
|
||||||
)
|
|
||||||
self.reloadData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func reloadData() {
|
func reloadData() {
|
||||||
// Fetch your app entities and recompute derived state.
|
// Fetch your app entities and recompute derived state.
|
||||||
refreshVersion &+= 1
|
refreshVersion &+= 1
|
||||||
@ -105,6 +94,29 @@ final class AppDataStore: SwiftDataCloudKitStore {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If your store needs post-remote side effects (for example widget timeline reload), use:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
startObservingCloudKitRemoteChanges {
|
||||||
|
WidgetCenter.shared.reloadAllTimelines()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If your store needs save side effects/error handling, prefer Bedrock protocol hooks:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
func didSaveAndReloadData() {
|
||||||
|
// optional side effects (widgets, cache invalidation, etc.)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSaveAndReloadError(_ error: Error) {
|
||||||
|
// set store error state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then call the shared default:
|
||||||
|
saveAndReload()
|
||||||
|
```
|
||||||
|
|
||||||
## 4) Integrate `SwiftDataRefreshThrottler`
|
## 4) Integrate `SwiftDataRefreshThrottler`
|
||||||
|
|
||||||
Use throttling for on-appear and tab/scene refresh paths:
|
Use throttling for on-appear and tab/scene refresh paths:
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
// SwiftDataStore.swift
|
// SwiftDataStore.swift
|
||||||
// Bedrock
|
// Bedrock
|
||||||
//
|
//
|
||||||
// Generic macOS fallback pulse wiring for SwiftData + CloudKit stores.
|
// Shared store protocols/defaults for SwiftData, including CloudKit helpers.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@ -15,6 +15,47 @@ public protocol SwiftDataStore: AnyObject {
|
|||||||
|
|
||||||
/// App-specific fetch/recompute entry point.
|
/// App-specific fetch/recompute entry point.
|
||||||
func reloadData()
|
func reloadData()
|
||||||
|
|
||||||
|
/// App-specific side effects after a successful save+reload cycle.
|
||||||
|
func didSaveAndReloadData()
|
||||||
|
|
||||||
|
/// App-specific error handling when save+reload fails.
|
||||||
|
func handleSaveAndReloadError(_ error: Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension SwiftDataStore {
|
||||||
|
/// Default no-op success hook.
|
||||||
|
func didSaveAndReloadData() {}
|
||||||
|
|
||||||
|
/// Default error handling hook.
|
||||||
|
func handleSaveAndReloadError(_ error: Error) {
|
||||||
|
Design.debugLog(
|
||||||
|
"\(String(describing: type(of: self))): failed saveAndReload error=\(error.localizedDescription)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Saves the context, reloads data, then runs optional hooks.
|
||||||
|
///
|
||||||
|
/// If hooks are omitted, protocol defaults are used:
|
||||||
|
/// - success: `didSaveAndReloadData()`
|
||||||
|
/// - failure: `handleSaveAndReloadError(_:)`
|
||||||
|
func saveAndReload(
|
||||||
|
onSuccess: (() -> Void)? = nil,
|
||||||
|
onFailure: ((Error) -> Void)? = nil
|
||||||
|
) {
|
||||||
|
do {
|
||||||
|
try modelContext.save()
|
||||||
|
reloadData()
|
||||||
|
(onSuccess ?? didSaveAndReloadData)()
|
||||||
|
} catch {
|
||||||
|
(onFailure ?? handleSaveAndReloadError)(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience overload that always uses protocol hooks.
|
||||||
|
func saveAndReload() {
|
||||||
|
saveAndReload(onSuccess: didSaveAndReloadData, onFailure: handleSaveAndReloadError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@ -38,6 +79,33 @@ public extension SwiftDataCloudKitStore {
|
|||||||
cloudKitSyncManager.hasReceivedRemoteChange(since: date)
|
cloudKitSyncManager.hasReceivedRemoteChange(since: date)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Starts observing remote CloudKit-backed store changes.
|
||||||
|
///
|
||||||
|
/// - Parameter afterReload: Optional callback that runs after the default `reloadData()` handling.
|
||||||
|
func startObservingCloudKitRemoteChanges(afterReload: (@MainActor () -> Void)? = nil) {
|
||||||
|
cloudKitSyncManager.startObserving { [weak self] in
|
||||||
|
self?.handleObservedCloudKitRemoteChange(afterReload: afterReload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies the default handling for an observed remote CloudKit merge.
|
||||||
|
///
|
||||||
|
/// - Parameter afterReload: Optional callback that runs after `reloadData()`.
|
||||||
|
func handleObservedCloudKitRemoteChange(afterReload: (@MainActor () -> Void)? = nil) {
|
||||||
|
let result = cloudKitSyncManager.processObservedRemoteChange(
|
||||||
|
modelContext: &modelContext,
|
||||||
|
modelContainer: modelContainer
|
||||||
|
)
|
||||||
|
|
||||||
|
Design.debugLog(
|
||||||
|
"\(String(describing: type(of: self))): received remote store change #\(result.eventCount); " +
|
||||||
|
"rebuiltContext=\(result.didRebuildModelContext)"
|
||||||
|
)
|
||||||
|
|
||||||
|
reloadData()
|
||||||
|
afterReload?()
|
||||||
|
}
|
||||||
|
|
||||||
/// Production fallback: pulses CloudKit import scheduling while app is active on Mac runtime.
|
/// Production fallback: pulses CloudKit import scheduling while app is active on Mac runtime.
|
||||||
func forceCloudKitImportPulse(reason: String) {
|
func forceCloudKitImportPulse(reason: String) {
|
||||||
guard cloudKitSyncManager.triggerMacImportPulse(
|
guard cloudKitSyncManager.triggerMacImportPulse(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user