Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Examples/Reminders/RemindersLists.swift
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,11 @@ struct RemindersListsView: View {
.listRowInsets(EdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 12))
}
}
.refreshable {
await withErrorReporting {
try await syncEngine.syncChanges()
}
}
.onAppear {
model.onAppear()
}
Expand Down
60 changes: 60 additions & 0 deletions Sources/SQLiteData/CloudKit/SyncEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,66 @@
}
}
}

/// Fetches pending remote changes from the server.
///
/// Use this method to ensure the sync engine immediately fetches all pending remote changes
/// before your app continues. This isn't necessary in normal use, as the engine automatically
/// syncs your app's records. It is useful, however, in scenarios where you require more control
/// over sync, such as pull-to-refresh.
///
/// - Parameter options: The options to use when fetching changes.
public func fetchChanges(
_ options: CKSyncEngine.FetchChangesOptions = CKSyncEngine.FetchChangesOptions()
) async throws {
let (privateSyncEngine, sharedSyncEngine) = syncEngines.withValue {
($0.private, $0.shared)
}
guard let privateSyncEngine, let sharedSyncEngine
else { return }
async let `private`: Void = privateSyncEngine.fetchChanges(options)
async let shared: Void = sharedSyncEngine.fetchChanges(options)
_ = try await (`private`, shared)
}

/// Sends pending local changes to the server.
///
/// Use this method to ensure the sync engine sends all pending local changes to the server
/// before your app continues. This isn't necessary in normal use, as the engine automatically
/// syncs your app's records. It is useful, however, in scenarios where you require greater
/// control over sync, such as a "Backup now" button.
///
/// - Parameter options: The options to use when sending changes.
public func sendChanges(
_ options: CKSyncEngine.SendChangesOptions = CKSyncEngine.SendChangesOptions()
) async throws {
let (privateSyncEngine, sharedSyncEngine) = syncEngines.withValue {
($0.private, $0.shared)
}
guard let privateSyncEngine, let sharedSyncEngine
else { return }
async let `private`: Void = privateSyncEngine.sendChanges(options)
async let shared: Void = sharedSyncEngine.sendChanges(options)
_ = try await (`private`, shared)
}

/// Synchronizes local and remote pending changes.
///
/// Use this method to ensure the sync engine immediately fetches all pending remote changes
/// _and_ sends all pending local changes to the server. This isn't necessary in normal use,
/// as the engine automatically syncs your app's records. It is useful, however, in scenarios
/// where you require greater control over sync.
///
/// - Parameters:
/// - fetchOptions: The options to use when fetching changes.
/// - sendOptions: The options to use when sending changes.
public func syncChanges(
fetchOptions: CKSyncEngine.FetchChangesOptions = CKSyncEngine.FetchChangesOptions(),
sendOptions: CKSyncEngine.SendChangesOptions = CKSyncEngine.SendChangesOptions()
) async throws {
try await fetchChanges(fetchOptions)
try await sendChanges(sendOptions)
}

private func cacheUserTables(recordTypes: [RecordType]) async throws {
try await userDatabase.write { db in
Expand Down
129 changes: 70 additions & 59 deletions Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -823,73 +823,84 @@
}
}
}
}

@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
@Test func generatedColumns() async throws {
try await userDatabase.userWrite { db in
try db.seed {
ModelA(id: 1, count: 42, isEven: true)
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
@Test func sendChanges() async throws {
try await userDatabase.userWrite { db in
try db.seed {
RemindersList(id: 1, title: "Personal")
}
}
try await syncEngine.sendChanges(CKSyncEngine.SendChangesOptions())
}
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
assertInlineSnapshot(of: container, as: .customDump) {
"""
MockCloudContainer(
privateCloudDatabase: MockCloudDatabase(
databaseScope: .private,
storage: [
[0]: CKRecord(
recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__),
recordType: "modelAs",
parent: nil,
share: nil,
count: 42,
id: 1
)
]
),
sharedCloudDatabase: MockCloudDatabase(
databaseScope: .shared,
storage: []

@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
@Test func generatedColumns() async throws {
try await userDatabase.userWrite { db in
try db.seed {
ModelA(id: 1, count: 42, isEven: true)
}
}
try await syncEngine.processPendingRecordZoneChanges(scope: .private)
assertInlineSnapshot(of: container, as: .customDump) {
"""
MockCloudContainer(
privateCloudDatabase: MockCloudDatabase(
databaseScope: .private,
storage: [
[0]: CKRecord(
recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__),
recordType: "modelAs",
parent: nil,
share: nil,
count: 42,
id: 1
)
]
),
sharedCloudDatabase: MockCloudDatabase(
databaseScope: .shared,
storage: []
)
)
)
"""
}
"""
}

let record = try syncEngine.private.database.record(for: ModelA.recordID(for: 1))
record.encryptedValues["isEven"] = false
try await syncEngine.modifyRecords(scope: .private, saving: [record]).notify()

assertInlineSnapshot(of: container, as: .customDump) {
"""
MockCloudContainer(
privateCloudDatabase: MockCloudDatabase(
databaseScope: .private,
storage: [
[0]: CKRecord(
recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__),
recordType: "modelAs",
parent: nil,
share: nil,
count: 42,
id: 1,
isEven: 0
)
]
),
sharedCloudDatabase: MockCloudDatabase(
databaseScope: .shared,
storage: []
let record = try syncEngine.private.database.record(for: ModelA.recordID(for: 1))
record.encryptedValues["isEven"] = false
try await syncEngine.modifyRecords(scope: .private, saving: [record]).notify()

assertInlineSnapshot(of: container, as: .customDump) {
"""
MockCloudContainer(
privateCloudDatabase: MockCloudDatabase(
databaseScope: .private,
storage: [
[0]: CKRecord(
recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__),
recordType: "modelAs",
parent: nil,
share: nil,
count: 42,
id: 1,
isEven: 0
)
]
),
sharedCloudDatabase: MockCloudDatabase(
databaseScope: .shared,
storage: []
)
)
)
"""
}
"""
}

try await userDatabase.read { db in
let modelA = try #require(try ModelA.find(1).fetchOne(db))
#expect(modelA.isEven == true)
try await userDatabase.read { db in
let modelA = try #require(try ModelA.find(1).fetchOne(db))
#expect(modelA.isEven == true)
}
}
}

}
#endif
Loading