Cirrus provides simple CloudKit sync for Codable Swift models. Rather than support every CloudKit feature, Cirrus is opinionated and prioritizes simplicity, reliability, and ergonomics with Swift value types.
Main Features
🙅
No more dealing with CKRecord, CKOperation, or CKSubscription
👀
Observe models and iCloud account changes with Combine
import CloudKitCodable
struct Landmark: CloudKitCodable {
struct Coordinate: Codable {
let latitude: Double
let longitude: Double
}
let identifier: UUID
let name: String
let coordinate: Coordinate
// MARK: - CloudKitCodable
/// A key that uniquely identifies the model. Use this identifier to update your
/// associated local models when the sync engine emits changes.
var cloudKitIdentifier: CloudKitIdentifier {
return identifier.uuidString
}
/// Managed by the sync engine, this should be set to nil when creating a new model.
/// Be sure to save this when persisting models locally.
var cloudKitSystemFields: Data? = nil
/// Describes how to handle conflicts between client and server models.
public static func resolveConflict(clientModel: Self, serverModel: Self) -> Self? {
// Use `cloudKitLastModifiedDate` to check when models were last saved to the server
guard let clientDate = clientModel.cloudKitLastModifiedDate,
let serverDate = serverModel.cloudKitLastModifiedDate else {
return clientModel
}
return clientDate > serverDate ? clientModel : serverModel
}
}
Initialize a SyncEngine for the model
import Cirrus
let syncEngine = SyncEngine<Landmark>()
Configure the SyncEngine to process remote changes
// Upload new or updated models
syncEngine.upload(newLandmarks)
// Delete models
syncEngine.delete(oldLandmark)
// Observe remote model changes
syncEngine.modelsChanged
.sink { change in
// Update local models
switch change {
case let .updated(models):
...
case let .deleted(modelIDs):
...
}
}
// Observe iCloud account status changes
syncEngine.$accountStatus
.sink { accountStatus in
switch accountStatus {
case .available:
...
case .noAccount:
...
...
}
}
And that’s it! Cirrus supports syncing multiple model types too, just initialize and configure a new SyncEngine for every type you want to sync.
To see an example of how Cirrus can be integrated into an app, clone this repository and open the CirrusExample Xcode project.
Installation
You can add Cirrus to an Xcode project by adding it as a package dependency.
From the File menu, select Swift Packages › Add Package Dependency…
If you have a single application target that needs access to the library, add both Cirrus and CloudKitCodable directly to your application.
If you have multiple targets where your models are in one target but you would like to handle syncing with Cirrus in another, then add CloudKitCodable to your model target and Cirrus to your syncing target.
Limitations
Cirrus only supports private iCloud databases. If you need to store data in a public iCloud database, Cirrus is not the right tool for you.
Nested Codable types on CloudKitCodable models will not be stored as separate CKRecord references; they are saved as Data blobs on the top level CKRecord. This leads to two important caveats:
CKRecord has a 1 MB data limit, so large models may not fit within a single record. The SyncEngine will not attempt to sync any models that are larger than 1 MB. If you are hitting this limitation, consider normalizing your data by creating discrete CloudKitCodable models that have identifier references to each other. You can use multiple SyncEngines to sync each model type.
If any child models have properties that reference on-disk file URLs, they will not be converted into CKAssets and stored in CloudKit. If you have a need to store files that are referenced by local file URLs on child models, you can override the Encodableencode(to:) and Decodableinit(from:) methods on your model to set the file URLs as keys on the coding container of the top level CloudKitCodable type. The SyncEngine will then be able to sync your files to iCloud.
License
This library is released under the MIT license. See LICENSE for details.
☁️ Cirrus
Cirrus provides simple CloudKit sync for
Codable
Swift models. Rather than support every CloudKit feature, Cirrus is opinionated and prioritizes simplicity, reliability, and ergonomics with Swift value types.CKRecord
,CKOperation
, orCKSubscription
Usage
After installing and following Apple’s steps for Enabling CloudKit in Your App:
CloudKitCodable
SyncEngine
for the modelSyncEngine
to process remote changesAnd that’s it! Cirrus supports syncing multiple model types too, just initialize and configure a new
SyncEngine
for every type you want to sync.To see an example of how Cirrus can be integrated into an app, clone this repository and open the CirrusExample Xcode project.
Installation
You can add Cirrus to an Xcode project by adding it as a package dependency.
Limitations
Cirrus only supports private iCloud databases. If you need to store data in a public iCloud database, Cirrus is not the right tool for you.
Nested
Codable
types onCloudKitCodable
models will not be stored as separateCKRecord
references; they are saved asData
blobs on the top levelCKRecord
. This leads to two important caveats:CKRecord
has a 1 MB data limit, so large models may not fit within a single record. TheSyncEngine
will not attempt to sync any models that are larger than 1 MB. If you are hitting this limitation, consider normalizing your data by creating discreteCloudKitCodable
models that have identifier references to each other. You can use multipleSyncEngine
s to sync each model type.CKAsset
s and stored in CloudKit. If you have a need to store files that are referenced by local file URLs on child models, you can override theEncodable
encode(to:)
andDecodable
init(from:)
methods on your model to set the file URLs as keys on the coding container of the top levelCloudKitCodable
type. TheSyncEngine
will then be able to sync your files to iCloud.License
This library is released under the MIT license. See LICENSE for details.
🙌 Special Thanks
Thanks to Tim Bueno for helping to build Cirrus.