drewmccormack / ensembles

A synchronization framework for Core Data.
MIT License
1.63k stars 132 forks source link

Using Ensembles with Swift project - Cloud data did download - But Data Doesn't Sync #223

Closed JDSX closed 8 years ago

JDSX commented 8 years ago

I have posted a detailed question on Stack-overflow with all the steps I have taken http://stackoverflow.com/questions/36462771/how-to-use-ensembles-framework-in-swift-project .

Basically I have manually added Ensembles, updated the core data stack and added notifications.

I have two devices logged into the same iCloud account, and neither data is shared to the other device, or when deleting the app and reinstalling the data is not retrieved from the iCloud and saved to the persistent store.

I do get the following NSLog when 'sometimes' saving data or loading up the app.

2016-04-06 13:17:37.101 APPNAME[435:152241] Cloud data did download

This is the outcome for the following appDelegate notification function

func cloudDataDidDownload(notif: NSNotification) {
NSLog("Cloud data did download")
CoreDataStack.defaultStack.syncWithCompletion(nil)
}

The notification is sent from this function in the CoreDataStack for when changes are merged.

func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, didSaveMergeChangesWithNotification notification: NSNotification!) {
    CoreDataStack.defaultStack.managedObjectContext.performBlockAndWait({ () -> Void in
        CoreDataStack.defaultStack.managedObjectContext.mergeChangesFromContextDidSaveNotification(notification)
    })
    if notification != nil {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.02 * Double(NSEC_PER_MSEC))), dispatch_get_main_queue(), {
            NSLog("Database was updated from iCloud")
            CoreDataStack.defaultStack.saveContext()
            NSNotificationCenter.defaultCenter().postNotificationName("DB_UPDATED", object: nil)
        })
    }
}

So everything seems like it should be working fine. I get no errors but the data is not syncing. I cannot tell if the problem is the data backing up to iCloud or retrieving from iCloud and merging with the persistent store. All I can tell is that data is not shared between devices using the same iCloud account and the app does not actually restore the data from the iCloud when reinstalling the app.

Where might I be going wrong ?

Might it have something to do with global identifiers, I am unsure how to use the identifiers for multiple entities. I have done the following for one of the entities.

func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, globalIdentifiersForManagedObjects objects: [AnyObject]!) -> [AnyObject]! {
    NSLog("%@", (objects as NSArray).valueForKeyPath("Entity1") as! [AnyObject])
    return (objects as NSArray).valueForKeyPath("Entity1") as! [AnyObject]
}
drewmccormack commented 8 years ago

Are you reloading your UI when the changes get merged? Can you set a breakpoint in the did-save delegate method, and see if there are actually new or changed objects in the userInfo of the NSNotification object?

You could also use CDESetCurrentLogLevel to set the logging to verbose, and see what is happening.

How much data are you syncing? Begin with very little. iCloud can take a while to transfer it, so you sometimes have to be patient. (CloudKit is much better.)

The Simple Sync in Swift example is a good one to see the steps needed in a very simple app.

In terms of global ids, I would say, welcome to the land of statically-typed pain. What an advancement this new approach is :)

In all seriousness, you will need a base class for your objects, or a protocol that your classes an conform to, and that has a uniqueIdentifier property or such.

BTW, the method you have now looks completely wrong, and could easily be why your are not seeing syncing. The method has to return unique ids for each of the objects that are passed in. Usually you store a UUID on each object. You cannot change this id after it is saved, or have two objects with the same id on the same device.

Drew

JDSX commented 8 years ago

The notification for cloudDataDidDownload wasn't being called in my viewController only in my app delegate - the notification for cloudDataDidDownload has been unpredictable and often not being called.

How would i use CDESetCurrentLogLevel ? I cannot find an example of it.

Basically the project has 5 Entities with properties containing strings, numbers and relationships. I haven't changed anything within the classes.

I have added the following code to my classes - unsure what the purpose of it is, I tried converting your Objective-C code to Swift for my classes,

class func ValueTypeInManagedObjectContext(context: NSManagedObjectContext) -> Entity  {
    let fetch : NSFetchRequest = NSFetchRequest(entityName: "Entity")
    var value : Entity? = nil
    do {
        try value = context.executeFetchRequest(fetch).last as? Entity
    } catch {
        value = nil
    }

    if value == nil {
        value = NSEntityDescription.insertNewObjectForEntityForName("Entity", inManagedObjectContext: context) as? Entity
        value!.index = NSNumber(bool: true)
    }

    return value!
}

I changed the iCloud container to a specific custom container and got the following error in the log,

Error in merge: Error Domain=DEErrorDomain Code=101 "(null)"

Then I remembered I also got this error for the previous container. Maybe this is an indication of what may be going wrong ?

PS: Still trying to figure out how to go about global identifiers as I am trying to work with an existing project where the classes don't have a unique Identifier property. I create a new core data model and migrate the old one, but need to figure out how to work with data already created without a identifier property.

drewmccormack commented 8 years ago

The notification for cloudDataDidDownload wasn't being called in my viewController only in my app delegate - the notification for cloudDataDidDownload has been unpredictable and often not being called.

You shouldn’t rely entirely on the notifications. Best to sync at other times, perhaps setting up a timer every few minutes. See our free book at http://leanpub.com/ensembles http://leanpub.com/ensembles

How would i use CDESetCurrentLogLevel ? I cannot find an example of it.

It's just a function. Searching the code should show how to do it. Might be in the book too.

    CDESetCurrentLoggingLevel(CDELoggingLevelVerbose);

Basically the project has 5 Entities with properties containing strings, numbers and relationships. I haven't changed anything within the classes.

I am unsure what the following code does, I tried converting your Objective-C code to Swift for my classes,

class func ValueTypeInManagedObjectContext(context: NSManagedObjectContext) -> Notebook { let fetch : NSFetchRequest = NSFetchRequest(entityName: "Notebook") var value : Notebook? = nil do { try value = context.executeFetchRequest(fetch).last as? Notebook } catch { value = nil }

if value == nil {
    value = NSEntityDescription.insertNewObjectForEntityForName("Notebook", inManagedObjectContext: context) as? Notebook
    value!.index = NSNumber(bool: true)
}

return value!

} You probably don’t need this. It is simple Core Data code, nothing to do with Ensembles. It is a method to try to fetch an object, and if it is not there, to create it. That’s all.

I changed the iCloud container to a specific custom container and got the following error in the log,

Error in merge: Error Domain=DEErrorDomain Code=101 "(null)"

Then I remembered I also got this error for the previous container. Maybe this is an indication of what may be going wrong ?

Have you registered your iCloud container in the developer portal?

And set it in your app settings?

Here is the Sample app in Swift: https://www.dropbox.com/s/qflbtokbe24noy8/Simple%20Sync%20in%20Swift.zip?dl=0 https://www.dropbox.com/s/qflbtokbe24noy8/Simple%20Sync%20in%20Swift.zip?dl=0

Drew

JDSX commented 8 years ago

Thanks for the sample app project (and the book) they are great help.

I guess the problem I am having is due to the global identifiers - I am under the impression they are required as it ensures data does not get corrupted. Therefore that is the reason my project isn't syncing.

I am trying to work with existing data and a existing core data model, and trying to add ensembles to the project. Neither of the classes have a Unique Identifier property, and even if I migrate my core data model to add a UI property to the classes the old data still would not have identifiers.

Would I have to add a unique ID for old data in order for it to sync ? Is so have you got any ideas on how to go about doing that.

How do I sync multiple entities with a unique ID property ? Here's you example of for syncing one class using it's unique ID.

I noticed with the sample app it has a property called uniqueIdentifier for the NumberHolder class but when is the identifier created, is it only used on sync ?

 func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble, globalIdentifiersForManagedObjects objects: [NSManagedObject]) -> [NSObject] {
    let numberHolders = objects as! [NumberHolder]
    return numberHolders.map { $0.uniqueIdentifier }
 }
drewmccormack commented 8 years ago

Yes, the best thing to do is read the chapter about Model in the book. It discusses different approaches to this.

Note that if you remove the global id delegate method, you should at least be able to test the sync a bit. It will generate its own global ids in that case. But you must completely remove that delegate method.

Drew

On 08 Apr 2016, at 00:03, jsingh notifications@github.com wrote:

Thanks for the sample app project (and the book) they are great help.

I guess the problem I am having is due to the global identifiers - I am under the impression they are required as it ensures data does not get corrupted.

I am trying to work with existing data and a existing core data model, and trying to add ensembles to the project. Neither of the classes have a Unique Identifier property, and even if I migrate my core data model to add a UI property to the classes the old data still would not have identifiers.

Have you got any suggestions for this dilemma of working with global identifiers for existing core data models of multiple entities ?

— You are receiving this because you commented. Reply to this email directly or view it on GitHub https://github.com/drewmccormack/ensembles/issues/223#issuecomment-207109261

JDSX commented 8 years ago

I mentioned in the other thread once i used on two devices and updated my core data stack similar to your example app I got it working. Thanks @drewmccormack you have done a great job.

PS: I am looking to purchase the CloudKit version in the future, would there be problems going from using Ensembles 1 & iCloud to version 2 and CloudKit ? And would roughly what would it take, for e.g. creating the DB on CloudKit for each data type and their properties

drewmccormack commented 8 years ago

It is fairly easy to move. Note that the old cloud data will not come into CloudKit. You start syncing over again.

To use CloudKit, you do some test syncing. That sets up the records in CloudKit. When you are ready, you put CloudKit in production mode and publish your app.

Kind regards, Drew

On 09 Apr 2016, at 16:21, jsingh notifications@github.com wrote:

I mentioned in the other thread once i used on two devices and updated my core data stack similar to your example app I got it working. Thanks @drewmccormack you have done a great job.

PS: I am looking to purchase the CloudKit version in the future, would there be problems going from using Ensembles 1 & iCloud to version 2 and CloudKit ? And would roughly what would it take, for e.g. creating the DB on CloudKit for each data type and their properties

— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub