Swift Ensembles Set Up & Ubiquitycontaineridentifier

Swift Ensembles Set Up & ubiquityContainerIdentifier

Got it to work in the end - found example apps in 1.0 Git

I belive I was leeching to fast - not giving enough time for the set up process to complete.

Support this framework - buy ensembles 2, if you like ver 1.

Update .. easier way

I just use the normal core data stack apple provides.

Here is the extras to get ensembles working.

var ensemble:CDEPersistentStoreEnsemble!

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

let file = CDEICloudFileSystem(ubiquityContainerIdentifier: nil)
let modelURL = NSBundle.mainBundle().URLForResource("DataModel", withExtension: "momd")!
let storeurl = self.applicationDocumentsDirectory.URLByAppendingPathComponent("store.sqlite")
ensemble = CDEPersistentStoreEnsemble(ensembleIdentifier: "MyStoreName", persistentStoreURL: storeurl, managedObjectModelURL: modelURL, cloudFileSystem: file)
ensemble.delegate = self

NSNotificationCenter.defaultCenter().addObserver(self, selector: "localSaveOccurred:", name: CDEMonitoredManagedObjectContextDidSaveNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "cloudDataDidDownload:", name: CDEICloudFileSystemDidDownloadFilesNotification, object: nil)

syncWithCompletion { completed in
if completed {
print("SUCCESSS")
}
else {
print("FAIL")
}
}

return true
}

// MARK: - Sync

func applicationDidEnterBackground(application: UIApplication) {
print("Did Enter Background Save from App Delegate")

let identifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
saveContext()

syncWithCompletion { (completed) -> Void in
if completed {
UIApplication.sharedApplication().endBackgroundTask(identifier)
}
}
}

func applicationWillEnterForeground(application: UIApplication) {
syncWithCompletion { (completed) -> Void in

}
}

func localSaveOccurred(note:NSNotification) {
syncWithCompletion { (completed) -> Void in

}
}

func cloudDataDidDownload(note:NSNotification) {
syncWithCompletion { (completed) -> Void in
print("items from iCloud arrived")
}
}

func syncWithCompletion(completion:(completed:Bool) -> Void) {

UIApplication.sharedApplication().networkActivityIndicatorVisible = true

if !ensemble.leeched {
ensemble.leechPersistentStoreWithCompletion(nil)
}
else {
ensemble.mergeWithCompletion{ error in
if error != nil {
print("cannot merge \(error!.localizedDescription)")
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
completion(completed: false)
}
else {
print("merged")
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
completion(completed: true)
}
}
}
}

// MARK: - Ensemble Delegate Methods

func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, didSaveMergeChangesWithNotification notification: NSNotification!) {

managedObjectContext.performBlockAndWait { () -> Void in
self.managedObjectContext.mergeChangesFromContextDidSaveNotification(notification)
}
}

func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, globalIdentifiersForManagedObjects objects: [AnyObject]!) -> [AnyObject]! {
return (objects as NSArray).valueForKeyPath("uniqueIdentifier") as! [AnyObject]
}

My First Way

Here it is in Swift, with a few extras

var ensemble:CDEPersistentStoreEnsemble!
var cloudFileSystem:CDEICloudFileSystem!
var managedObjectContext: NSManagedObjectContext!

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.

setUpCoreData()

let modelURL = NSBundle.mainBundle().URLForResource("YourDataModel", withExtension: "momd")!
cloudFileSystem = CDEICloudFileSystem(ubiquityContainerIdentifier:"USE_YOUR_APPS_REVERSE DOMAIN NAME HERE")

From the developer: RE ubiquityContainerIdentifier

This is not part of Ensembles per se. It is from iCloud. Every app
using iCloud has to have a ubiquity container id. You can find it in
your app settings when you enable iCloud. It is unique per app, and we
only use it if you are choosing for iCloud (eg not Dropbox).

    ensemble = CDEPersistentStoreEnsemble(ensembleIdentifier: "store", persistentStoreURL: storeURL(), managedObjectModelURL: modelURL, cloudFileSystem: cloudFileSystem!)
ensemble.delegate = self

NSNotificationCenter.defaultCenter().addObserver(self, selector: "localSaveOccurred:", name: CDEMonitoredManagedObjectContextDidSaveNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "cloudDataDidDownload:", name: CDEICloudFileSystemDidDownloadFilesNotification, object: nil)

syncWithCompletion { completed in
if completed {
print("SUCCESSS")
}
else {
print("FAIL")
}
}

return true
}

// MARK: - Core Data Stack

func setUpCoreData() {

let modelURL = NSBundle.mainBundle().URLForResource("DataModel", withExtension: "momd")!
guard let model = NSManagedObjectModel(contentsOfURL: modelURL) else { fatalError("cannot use model") }

do {
try NSFileManager.defaultManager().createDirectoryAtURL(storeDirectoryURL(), withIntermediateDirectories: true, attributes: nil)
}
catch {
fatalError("cannot create dir")
}

let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
//NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption: @YES, NSInferMappingModelAutomaticallyOption: @YES};

let failureReason = "There was an error creating or loading the application's saved data."

do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL(), options: nil)

managedObjectContext = NSManagedObjectContext.init(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason

dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() 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.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
}

func storeDirectoryURL() -> NSURL {

let directoryURL = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
return directoryURL
}

func storeURL() -> NSURL {
let url = storeDirectoryURL().URLByAppendingPathComponent("store.sqlite")
return url
}

// MARK: - Sync

func applicationDidEnterBackground(application: UIApplication) {
print("Did Enter Background Save from App Delegate")

let identifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
saveContext()

syncWithCompletion { (completed) -> Void in
if completed {
UIApplication.sharedApplication().endBackgroundTask(identifier)
}
}
}

func applicationWillEnterForeground(application: UIApplication) {
syncWithCompletion { (completed) -> Void in

}
}

func localSaveOccurred(note:NSNotification) {
syncWithCompletion { (completed) -> Void in

}
}

func cloudDataDidDownload(note:NSNotification) {
syncWithCompletion { (completed) -> Void in

}
}

func syncWithCompletion(completion:(completed:Bool) -> Void) {

if !ensemble.leeched {
ensemble.leechPersistentStoreWithCompletion { error in
if error != nil {
print("cannot leech \(error!.localizedDescription)")
completion(completed: false)
}
else {
print("leached!!")
completion(completed: true)
}
}
}
else {
ensemble.mergeWithCompletion{ error in
if error != nil {
print("cannot merge \(error!.localizedDescription)")
completion(completed: false)
}
else {
print("merged!!")
completion(completed: true)
}
}
}
}

// MARK: - Ensemble Delegate Methods

func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, didSaveMergeChangesWithNotification notification: NSNotification!) {

print("did merge changes with note")

managedObjectContext.mergeChangesFromContextDidSaveNotification(notification)
}

func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, globalIdentifiersForManagedObjects objects: [AnyObject]!) -> [AnyObject]! {
return (objects as NSArray).valueForKeyPath("uniqueIdentifier") as! [AnyObject]
}

Where do I place the Ensembles uniqueIdentifier?

Each entity needs an attribute for the unique identifier. The value should be unique for each instance of that entity.

The value of the unique identifier must be unique among other instances of the same entity type.

If you have three entities, Entity1, Entity2, and Entity3 then you can have an instance of Entity1 with identifier '42' and an instance of Entity2 with identifier '42 and another instance of Entity3 with identifier '42' without causing any problems.

EDIT

You say each entity needs an attribute... which should be unique for
each instance of that entity. The value must be unique among other
instances... but then you give examples of Entity1, 2 and 3 where the
identifier is the same for all three! I'm confused... – SpokaneDude

That's correct. Entities describe the attributes and relationships for a class of managed objects (which is why the class that represents an entity is named NSEntityDescription). The entity itself is not a managed object.

So, for each entity defined in your model, you need to have a unique identifier attribute. The value of that unique identifier must be unique among instances of that particular kind of entity.

If you have 100 instances of Entity1 then each of those instances must have a different value for the unique identifier, relative to all other instances of Entity1.

If you have 100 instances of Entity2 then each of those instances must have a different value for the unique identifier, relative to all other instances of Entity2 but they don't have to be unique among the identifiers for the instances of Entity1 because the identifiers must be unique per-entity.

OK, one last question (I hope). The app is for bookstores; does each
unique bookstore have one (1) UUID that they share amoung the
different employee's iPads? and if I have another app (say for
barbershop scheculing), is the UUID different than the bookstore's
UUID? And lastly, does the UUID value ever change for a particular
bookstore? – SpokaneDude

So, I assume your core data model has an entity named Bookstore with some number of attributes. You need to make sure that one of those attributes will uniquely identify each managed object instance of entity Bookstore.

If you have an attribute name and you know for certain that you will never have two Bookstore instances with the same name, then you can use name as your unique identifier.

However, if there is the possibility that there may be more than one object instance that may have the same attributes, yet still represent a different object, then you need a special attribute whose sole purpose is to guarantee uniqueness.

For example, let's say your application sucks down JSON data from a server. At the same time, your user runs the app on their iPad and iPhone. They both read a record that describes a Bookstore with name "Bob's Books" and address "42 Mall Drive."

Each device creates the instance in their local database.

When it comes time to synchronize those two instances, how does the synchronization algorithm know if you have two completely different objects with the same value, or if you have two copies of the exact same object?

That's where the unique identifier comes in. You tell the ensembles framework which attribute of each entity can be used to tell if the objects are really the same or not. Then, when it sees two different objects, it can look at the unique identifier and determine if the two objects are really the same object, or if they are two completely separate instances.

Thus, for each entity, you need to either ensure that one of the attributes can always be used to uniquely identify objects in this manner. If you don't have such an attribute in your model, then you should add one for the entity, and make sure that the attribute is unique among instances of that particular entity.

The unique identifier only has to be unique among instances of the same entity, within the same database. However, when I have not had a certain unique attribute, I have been using NSUUID to generate my unique identifiers.

Ignore property when syncing

You can add CDEIgnoredKey to the property in the user info of the model, with a value 1, and Ensembles will not sync the property.

iCloud - NSCoding or Core Data

iCloud support may work well with CoreData by now. However, it was so broken for so long that I (and others) completely stopped using it. I wasted so much time trying to get it to work, that I refuse to spend one more second trying to ever use it again.

After writing my own proprietary CoreData syncing solution, I finally tried Ensembles and I've never looked back.

However, if your application does not currently require CoreData, why not use the standard iCloud support? It seems to work fine for normal file syncing, which is what you seem to have if you are just saving objects to file.

No need to add the complexities of CoreData if your app is working just fine without it.

Getting Ensembles Idiomatic OSX App to sync with iCloud

Ok, for quite some time, I didn't have that error again on my Mac and wanted to share, that I have NOT found out, WHAT caused this iCloud syncing error, but that it worked now for weeks, AFTER I restored my ~/Library folder again.

So, my best guess for solving this error of mine: Do TimeMachine Backups and do NOT manually delete things under ~/Library without such a backup!!!



Related Topics



Leave a reply



Submit