Compatibility
- 1.0.0 and master5.35.25.15.04.2
- 1.0.0 and masteriOSmacOS(Intel)macOS(ARM)LinuxtvOSwatchOS
Stitch is a swift library to sync a CoreData store to CloudKit
Platform | Tests | Coverage |
---|---|---|
macOS | ||
iOS | ||
tvOS | ||
watchOS | build info only - soon |
Stitch like it's namesake has some prickly parts to be aware of right off the bat.
It was built primarily for my needs, and I haven't needed these, but I am not opposed to working in support for those that make sense
So after seeing that scary list of sharp points, what does Stitch have to offer?
CoreData | CloudKit |
---|---|
Date | Date/Time |
Data | Bytes |
External Stored Data | CKAsset |
String | String |
Int16 | Int(64) |
Int32 | Int(64) |
Int64 | Int(64) |
Decimal | Double |
Float | Double |
Boolean | Int(64) |
NSManagedObject | Reference |
CoreData Relationship | Translation on CloudKit |
---|---|
To - one | To one relationships are translated as CKReferences in the CloudKit Container. |
To - many | To many relationships are not explicitly created, Stitch only creates and manages to-one relationships in CloudKit and then translates those back when syncing down.Example -> If an Employee has a to-one relationship to Department and Department has a to-many relationship to Employee than Stitch will only link the employee to the department in CloudKit. During sync, it translates these back in the local store |
many - many | Stitch does not support these directly. You can create them though using a linking table |
Note : You must create inverse relationships in your app's CoreData Model or Stitch won't be able to translate CoreData Models in to CloudKit Records. Unexpected errors and curroption of data can possibly occur.
Stitch keeps the CoreData store in sync with the CloudKit Servers.
After a sync completes, you must integrate the changes in to your UI. More on this to come.
In case of any sync conflicts, Stitch exposes 2 conflict resolution policies. Defined in StitchStore.ConflictPolicy
serverWins
- This is the default. It considers the server record as the true record.clientWins
- This considers the client record as the true record.StitchStore.storeType
to your app's NSPersistentStoreCoordinator and assign it to the property created in the previous step.
Pass in an appropriate options dictionary, more on supported options below.do {
let store = try coordinator.addPersistentStoreWithType(StitchStore.storeType,
configuration: nil,
URL: url,
options: options) as? StitchStore
store?.triggerSync(.storeAdded)
} catch {
print("There was an error adding the store! \(error)")
}
UIApplication.shared.registerForRemoteNotifications()
NSApp.registerForRemoteNotifications(matching: .alert)
handlePush
on the instance of SMStore created earlier. (macOS shown)func application(application: UIApplication,
didReceiveRemoteNotification userInfo: [NSObject : AnyObject])
{
self.smStore?.handlePush(userInfo: userInfo)
}
NotificationCenter.default.addObserver(self,
selector: #selector(cloudKitSyncComplete),
name: StitchStore.Notifications.DidFinishSync,
object: nil)
func realObjectsFromCloudKitSyncIDs(ids: Array<NSManagedObjectID>) -> Set<NSManagedObject> {
return Set<NSManagedObject>(ids.compactMap { persistentContainer.viewContext.object(with: $0) })
}
@objc func cloudKitSyncComplete(note: NSNotification) {
let noteInserted = note.userInfo?[NSInsertedObjectsKey] as? Array<NSManagedObjectID> ?? []
let noteDeleted = note.userInfo?[NSDeletedObjectsKey] as? Array<NSManagedObjectID> ?? []
let noteUpdated = note.userInfo?[NSUpdatedObjectsKey] as? Array<NSManagedObjectID> ?? []
if noteInserted.count == 0 && noteDeleted.count == 0 && noteUpdated.count == 0 {
return
}
let objectArrays = [NSInsertedObjectsKey: realObjectsFromCloudKitSyncIDs(ids: noteInserted),
NSDeletedObjectsKey: realObjectsFromCloudKitSyncIDs(ids: noteDeleted),
NSUpdatedObjectsKey: realObjectsFromCloudKitSyncIDs(ids: noteUpdated)]
let modifiedNote = Notification(name: .NSManagedObjectContextDidSave,
object: note.object,
userInfo: objectArrays)
persistentContainer.viewContext.mergeChanges(fromContextDidSave: modifiedNote)
}
Swift targets managed by Swift Package Manager can't be imported in to Obj-C code directly. You will need to use some swift code around touching stuff in the Stitch module.
If you want to use it in a framework, such as might be shared between main app and extensions, Xcode tries to add it as an import to the generated [ModuleName]-Swift.h for the framework which will cause it to error. This can be fixed by adding a private module map to the framework target. Make a file with the extension .modulemap with the contents
module Stitch {
export *
}
Then in the build settings put your modulemap's project relative path in the setting MODULEMAP_PRIVATE_FILE
.
Stitch won't be accessible from Objective-C either in the framework or the app that links against it, but it will be available in Swift in either.
(There may be better ways of dealing with this, but it has worked for me)
Defined in StitchStore.Options
FetchRequestPredicateReplacement
NSNumber boolean that enables replacement of NSManagedObjects in NSPredicates on NSFetchRequests.
Defaults to false
.
Requires objects to be replaced be saved prior to replacing them, otherwise errors will be thrown.
Supports replacing managed objects in: "keyPath == %@"
, "keyPath in %@"
and "%@ contains %@"
predicates, as well as compound predicates with those as sub predicates.
SyncConflictResolutionPolicy
is an NSNumber of the raw value of one of the options in StitchStore.ConflictPolicy
.
Defaults to StitchStore.ConflictPolicy.serverWins
CloudKitContainerIdentifier
is a String identifying which CloudKit container ID to use if your app uses an identifier which does not match your Bundle ID.
Defaults to using CKContainer.default().privateCloudDatabase
. Important that your app on different platforms all be set to use the same CloudKit container.
ConnectionStatusDelegate
an object which conforms to StitchConnectionStatus
for asking whether we have an internet connection at the moment
ExcludedUnchangingAsyncAssetKeys
is an array of Strings which indicate keys which should not be synced down during the main cycle due to being a large CKAsset
Syncing down can be done later on request or demand based on application need.
Your asset containing properties should not overlap in name with other keys you want synced down to use this.
Defaults to nil
.
BackingStoreType
is a string which defines what type of backing store is to be used. Defaults to NSSQLiteStoreType
and testing is done against this type. Other stores may have issues.
SyncOnSave
an NSNumber boolean value for whether to automatically sync when the database is told to save.
Defaults to true
.
ZoneNameOption
Lets you specify a string to use as the CloudKitZone ID name.
Defaults to StitchStore.SubscriptionInfo.CustomZoneName
SubscriptionNameOption
Lets you specify a string to use as the CloudKit subscription name ID.
Defaults to StitchStore.SubscriptionInfo.SubscriptioName
Xcode 11
Swift 5.1
Simply add the git url to Swift Package Manager in Xcode 11
Stitch was created by Elizabeth Siemer, based on a heavily modified by me fork of Seam by Nofel Mahmood but it has been nearly completely rewritten.
Follow Elizabeth on Twitter and GitHub or email her at elizabeth@darkchocolatesoftware.com
Stitch is available under the MIT license. See the LICENSE file for more info.