Seam3 is a framework built to bridge gaps between CoreData and CloudKit. It almost handles all the CloudKit hassle.
All you have to do is use it as a store type for your CoreData store.
Local caching and sync is taken care of.
Corrects one-to-many and many-to-one relationship mapping between CoreData and CloudKit
Adds mapping between binary attributes in CoreData and CKAssets in CloudKit
Code updates for Swift 3.0 on iOS 10, Mac OS 10.11 & tvOS 10
Restructures code to eliminate the use of global variables
CoreData to CloudKit
Attributes
CoreData
CloudKit
NSDate
Date/Time
Binary Data
Bytes or CKAsset (See below)
NSString
String
Integer16
Int(64)
Integer32
Int(64)
Integer64
Int(64)
Decimal
Double
Float
Double
Boolean
Int(64)
NSManagedObject
Reference
In the table above :Integer16, Integer32, Integer64, Decimal, Float and Boolean are referring to the instance of NSNumber used
to represent them in CoreData Models. NSManagedObject refers to a to-one relationship in a CoreData Model.
If a Binary Data attribute has the Allows External Storage option selected, it will be stored as a CKAsset in Cloud Kit, otherwise it will be stored as Bytes in the CKRecord itself.
Relationships
CoreData Relationship
Translation on CloudKit
To - one
To one relationships are translated as CKReferences on the CloudKit Servers.
To - many
To many relationships are not explicitly created. Seam3 only creates and manages to-one relationships on the CloudKit Servers. Example -> If an Employee has a to-one relationship to Department and Department has a to-many relationship to Employee than Seam3 will only create the former on the CloudKit Servers. It will fullfil the later by using the to-one relationship. If all employees of a department are accessed Seam3 will fulfil it by fetching all the employees that belong to that particular department.
Note : You must create inverse relationships in your app’s CoreData Model or Seam3 wouldn’t be able to translate CoreData Models in to CloudKit Records. Unexpected errors and corruption of data can possibly occur.
Sync
Seam3 keeps the CoreData store in sync with the CloudKit Servers. It let’s you know when the sync operation starts and finishes by throwing the following two notifications.
smSyncDidStartNotification
smSyncDidFinishNotification
If an error occurred during the sync operation, then the userInfo property of the smSyncDidFinish notification will contain an Error object for the key SMStore.SMStoreErrorDomain
Conflict Resolution Policies
In case of any sync conflicts, Seam3 exposes 3 conflict resolution policies.
clientTellsWhichWins
This policy requires you to set the syncConflictResolutionBlock property of your SMStore. The closure you specify will receive three CKRecord arguments; The first is the current server record. The second is the current client record and the third is the client record before the most recent change. Your closure must modify and return the server record that was passed as the first argument.
serverRecordWins
This is the default. It considers the server record as the true record.
clientRecordWins
This considers the client record as the true record.
How to use
Declare a SMStore type property in the class where your CoreData stack resides.
var smStore: SMStore?
For iOS9 and earlier or macOS, add a store type of SMStore.type to your app’s NSPersistentStoreCoordinator and assign it to the property created in the previous step.
```swift
- For iOS10 using `NSPersistentContainer`:
```swift
lazy var persistentContainer: NSPersistentContainer = {
SMStore.registerStoreClass()
let container = NSPersistentContainer(name: "Seam3Demo2")
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
if let applicationDocumentsDirectory = urls.last {
let url = applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite")
let storeDescription = NSPersistentStoreDescription(url: url)
storeDescription.type = SMStore.type
container.persistentStoreDescriptions=[storeDescription]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}
fatalError("Unable to access documents directory")
}()
By default, logs will be written to os_log, but you can route log messages to your own class by extending SMLogger:
class AppDelegate: SMLogDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
SMStore.logger = self
}
// MARK: SMLogDelegate
func log(_ message: @autoclosure() -> String, type: SMLogType) {
#if DEBUG
switch type {
case .debug:
print("Debug: \(message())")
case .info:
print("Info: \(message())")
case .error:
print("Error: \(message())")
case .fault:
print("Fault: \(message())")
case .defaultType:
print("Default: \(message())")
}
#endif
}
}
The default Cloud Kit container is named using your app or application’s bundle identifier. If you want to share Cloud Kit data between apps on different platforms (e.g. iOS and macOS) then you need to use a named Cloud Kit container. You can specify a cloud kit container when you create your SMStore instance.
On iOS10, specify the SMStore.SMStoreContainerOption using the NSPersistentStoreDescription object
let storeDescription = NSPersistentStoreDescription(url: url)
storeDescription.type = SMStore.type
storeDescription.setOption("iCloud.org.cocoapods.demo.Seam3-Example" as NSString, forKey: SMStore.SMStoreContainerOption)
On iOS9 and macOS specify an options dictionary to the persistent store coordinator
Ensure that you specify the value you specify is selected under iCloud containers on the capabilities tab for your app in Xcode.
Migrating from Seam to Seam3
Migration should be quite straight-forward, as the format used to store data in CloudKit and in the local backing store haven’t changed.
Change the import statement to import Seam3 and you should be good to go.
Example
To run the example project, clone the repo, and run pod install from the Example directory first. If you are running on the simulator, make sure that you log in to iCloud using the settings app in the simulator.
Installation
Seam3 is available through CocoaPods. To install
it, simply add the following line to your Podfile:
Seam3
Seam3 is a framework built to bridge gaps between CoreData and CloudKit. It almost handles all the CloudKit hassle. All you have to do is use it as a store type for your CoreData store. Local caching and sync is taken care of.
Seam3 is based on Seam by nofelmahmood
Changes in Seam3 include:
CoreData to CloudKit
Attributes
In the table above :
Integer16
,Integer32
,Integer64
,Decimal
,Float
andBoolean
are referring to the instance ofNSNumber
used to represent them in CoreData Models.NSManagedObject
refers to ato-one relationship
in a CoreData Model.If a
Binary Data
attribute has the Allows External Storage option selected, it will be stored as aCKAsset
in Cloud Kit, otherwise it will be stored asBytes
in theCKRecord
itself.Relationships
Example -> If an Employee has a to-one relationship to Department and Department has a to-many relationship to Employee than Seam3 will only create the former on the CloudKit Servers. It will fullfil the later by using the to-one relationship. If all employees of a department are accessed Seam3 will fulfil it by fetching all the employees that belong to that particular department.
Note : You must create inverse relationships in your app’s CoreData Model or Seam3 wouldn’t be able to translate CoreData Models in to CloudKit Records. Unexpected errors and corruption of data can possibly occur.
Sync
Seam3 keeps the CoreData store in sync with the CloudKit Servers. It let’s you know when the sync operation starts and finishes by throwing the following two notifications.
smSyncDidStart
Notification
smSyncDidFinish
Notification
If an error occurred during the sync operation, then the
userInfo
property of thesmSyncDidFinish
notification will contain anError
object for the keySMStore.SMStoreErrorDomain
Conflict Resolution Policies
In case of any sync conflicts, Seam3 exposes 3 conflict resolution policies.
clientTellsWhichWins
This policy requires you to set the
syncConflictResolutionBlock
property of yourSMStore
. The closure you specify will receive threeCKRecord
arguments; The first is the current server record. The second is the current client record and the third is the client record before the most recent change. Your closure must modify and return the server record that was passed as the first argument.serverRecordWins
This is the default. It considers the server record as the true record.
clientRecordWins
This considers the client record as the true record.
How to use
SMStore.type
to your app’s NSPersistentStoreCoordinator and assign it to the property created in the previous step. ```swiftSMStore.registerStoreClass() do { self.smStore = try coordinator.addPersistentStoreWithType(SMStore.type, configuration: nil, URL: url, options: nil) as? SMStore }
By default, logs will be written to
os_log
, but you can route log messages to your own class by extendingSMLogger
:You can access the
SMStore
instance using:Before triggering a sync, you should check the Cloud Kit authentication status and check for a changed Cloud Kit user:
Enable Push Notifications for your app.
Implement didReceiveRemoteNotification Method in your AppDelegate and call
handlePush
on the instance of SMStore created earlier.Enjoy
Cross-platform considerations
The default Cloud Kit container is named using your app or application’s bundle identifier. If you want to share Cloud Kit data between apps on different platforms (e.g. iOS and macOS) then you need to use a named Cloud Kit container. You can specify a cloud kit container when you create your SMStore instance.
On iOS10, specify the
SMStore.SMStoreContainerOption
using theNSPersistentStoreDescription
objectOn iOS9 and macOS specify an options dictionary to the persistent store coordinator
Ensure that you specify the value you specify is selected under iCloud containers on the capabilities tab for your app in Xcode.
Migrating from Seam to Seam3
Migration should be quite straight-forward, as the format used to store data in CloudKit and in the local backing store haven’t changed. Change the import statement to
import Seam3
and you should be good to go.Example
To run the example project, clone the repo, and run
pod install
from the Example directory first. If you are running on the simulator, make sure that you log in to iCloud using the settings app in the simulator.Installation
Seam3 is available through CocoaPods. To install it, simply add the following line to your Podfile:
Author
paulw, paulw@wilko.me
License
Seam3 is available under the MIT license. See the LICENSE file for more info.