A Swift package that provides seamless CloudKit synchronization capabilities for your iOS and macOS applications. SyncEngine handles all the complexity of CloudKit sync, including conflict resolution, offline support, and automatic retries.
- iOS 13.0+ / macOS 12.0+
- Swift 5.9+
- CloudKit enabled in your application
- 🔄 Automatic synchronization with CloudKit
- 📱 Offline support with automatic sync when connection is restored
- 🔒 Private database support
- 🔍 Change tracking and conflict resolution
- 📦 Custom zone management
- 📡 Network status monitoring
- 👤 iCloud account status monitoring
- ⚡️ Efficient batch operations
- 🔄 Automatic retry mechanism for failed operations
Add this package to your Xcode project using Swift Package Manager:
dependencies: [
.package(url: "https://github.com/iSapozhnik/SyncEngine.git", from: "1.0.0")
]
First, create a configuration that implements SyncEngineConfig
:
struct MySyncConfig: SyncEngineConfig {
let containerIdentifier: String = "iCloud.com.yourapp.container"
let zoneName: String = "YourZoneName"
let ownerName: String? = nil // Uses current user by default
}
Implement the Syncable
protocol for any model you want to sync:
struct Note: Syncable {
var ckData: Data? = nil
let id: String
let text: String
}
let syncEngine = SyncEngine(
syncConfig: MySyncConfig(),
defaults: UserDefaults.standard,
initialModels: [Note(text: "Hello, world!")]
)
// Handle model updates
syncEngine.didUpdateModels = { modelsByType in
// Update your local database with the changes
}
// Handle deletions
syncEngine.didDeleteModels = { recordIDs in
// Remove deleted records from your local database
}
The SyncEngine will automatically sync when:
- The app becomes active
- Network connectivity is restored
- Manual sync is triggered
To manually trigger a sync:
do {
try await syncEngine.performSync()
} catch {
print("Sync failed: \(error)")
}
// Observe account status changes
syncEngine.$accountStatus
.sink { status in
switch status {
case .available:
print("iCloud account is available")
case .restricted:
print("iCloud account is restricted")
case .noAccount:
print("No iCloud account")
case .couldNotDetermine:
print("Could not determine account status")
default:
break
}
}
.store(in: &cancellables)
// Observe network availability
syncEngine.$isNetworkAvailable
.sink { isAvailable in
if isAvailable == true {
print("Network is available")
} else {
print("Network is unavailable")
}
}
.store(in: &cancellables)
// Observe sync state changes
syncEngine.$state
.sink { state in
switch state {
case .idle:
print("Sync is idle")
case .loading:
print("Sync is in progress")
}
}
.store(in: &cancellables)
syncEngine.progressHandler = { progress in
print("Sync progress: \(progress * 100)%")
}
-
Error Handling: Always implement proper error handling for sync operations
do { try await syncEngine.performSync() } catch { if let cloudError = error as? CKError { // Handle specific CloudKit errors } // Handle other errors }
-
Conflict Resolution: Implement proper conflict resolution in your Syncable models
static func resolveConflict(clientRecord: CKRecord, serverRecord: CKRecord) -> CKRecord { // Example: Server wins strategy return serverRecord // Or implement custom merge logic // let mergedRecord = serverRecord // mergedRecord["field"] = determineWinningValue(client: clientRecord, server: serverRecord) // return mergedRecord }
-
Data Consistency: Keep local cache in sync with remote changes
syncEngine.didUpdateModels = { modelsByType in // Update local database for (type, models) in modelsByType { database.update(models) } } syncEngine.didDeleteModels = { recordIDs in // Remove from local database database.delete(recordIDs) }
-
Resource Management: Stop monitoring when appropriate
// In your cleanup code syncEngine.stopMonitoring()
- Requires iOS 13.0+ or macOS 12.0+
- Only supports private database operations
- Requires active iCloud account
- Network connectivity required for sync operations (though offline changes are queued)
Contributions are welcome! Please feel free to submit a Pull Request.
[Your license information here]