6

I can't handle this problem, I'm trying to integrate iCloud and Core Data to my app and I stuck with iCloud synchronization part.

My complete scenario:

  1. Local core data storage seeded with initial data
  2. Later the app ask the user about iCloud or local data stoge
  3. If user choose iCloud, current local storage migrates to iCloud store
  4. After migration, context and persistent store coordinator is reloaded with iCloud Store
  5. Refetch data from new context (Trouble here)

If we take away discussion about migration and focus on loading persistent store coordinator with iCloud, I think that the problem is related to
NSPersistentStoreCoordinatorStoresDidChangeNotification event.

I just don't understand it. In every article that I read, I saw something like: "reload you UI here". But it doesn't work for me.

Reload coordinator function

func configureContext() -> NSManagedObjectContext? {
    // Create the coordinator and context
    let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)

    //remove all observers from "previous" coordinator
    deregisterForStoreChanges()
    //and register them for new one
    registerObservers(persistentStoreCoordinator: coordinator)

    do {

        //storeSettings.url = applicationDocumentsDirectory.URLByAppendingPathComponent("iCloud.sqlite")
        //storeSettings.options = [NSPersistentStoreUbiquitousContentNameKey:"iCloudProject"]

        try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeSettings.url, options: storeSettings.options)

    } catch {
        //Here was standard error handler I didn't changed it, but removed for listing
        abort()
    }
    persistentStoreCoordinator = coordinator

    let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
    managedObjectContext.persistentStoreCoordinator = coordinator

    //not quite sure about the next string, didn't test it yet
    managedObjectContext.mergePolicy = NSMergePolicy(mergeType:NSMergePolicyType.MergeByPropertyStoreTrumpMergePolicyType )

    //tried to refetch my records here but, with no luck.
    //refetchUIRecords()
    return managedObjectContext
}

And my NSPersistentStoreCoordinatorStoresDidChangeNotification event func

func persistentStoreCoordinatorDidChangeStores(notification:NSNotification){

    if notification.userInfo?[NSAddedPersistentStoresKey] != nil {
        print("-----------------------")
        print("ADDED")
        print("-----------------------")
        //deduplicateRecords()

        //if iCloud -> refetch UI
        if NSUserDefaults.standardUserDefaults().boolForKey("iCloud"){
            self.createTimer()
        }
    }
    print("Did Change")

}

Create timer function is just a function which wait for 1 secods before actual refetching and refreshing UI.

The problem

When we reach step 4 from my scenario, and call configureContext function, I see this in the console:

2016-04-12 13:31:27.749 TestingiCloudProject[2052:1142902] -[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](898): CoreData: Ubiquity:  mobile~567404C0-9D84-4C07-A0F8-D25832CB65D8:iCloudProject
Using local storage: 1 for new NSFileManager current token <ef7a917f bca47ecf 5d58862d cbe9998d 7e53e5ea>
Did Change
Did Change
-----------------------
ADDED
-----------------------
Timer
Did Change
Refetch
Refetching...
Number of records after fetch: 1 //must be more than 1 
2016-04-12 13:31:30.090 TestingiCloudProject[2052:1143055] -[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](898): CoreData: Ubiquity:  mobile~567404C0-9D84-4C07-A0F8-D25832CB65D8:iCloudProject
Using local storage: 0 for new NSFileManager current token <ef7a917f bca47ecf 5d58862d cbe9998d 7e53e5ea>

As you can see my fetch request executes before Using local storage: 0 and that's why I'm recieving records from the local storage (1 record) not iCloud (which contains more than 1).

I don't understand when to refetch my records. I take timer part from this great source for all who want to know more about Core Data and iCloud. But, 1 second isn't enought, 2 seconds is work as I want, but what if iCloud store will be bigger than mine, or network connection will be worse than mine, I don't think that timer is the way.

I hope somebody already face this trivial problem.

EDIT

I didn't find any help from Apple dev forum and SO, so I activated my code tech support token, I hope they can help me. As soon as I'll solve my problem I'll write an answer. But, if you, who read this question, know the possible answer, post it now.

Dima Deplov
  • 3,688
  • 7
  • 45
  • 77
  • I'm having same issue. Did you ever figure out a better way than a timer? – Josh Apr 09 '17 at 05:16
  • 1
    @Josh I moved from Core Data iCloud sync to Core Data + CloudKit sync. Since Apple Notes, Reminders and other apps moved to CloudKit I think it's a way to go. I recommend you look at WWDC videos about CloudKit. I also have troubles with CloudKit but I handle them, you can take a look http://stackoverflow.com/questions/37061665/cloudkit-ckfetchrecordchangesoperation-ckserverchangetoken-and-delta-download – Dima Deplov Apr 09 '17 at 12:36

1 Answers1

1

There is another notification NSPersistentStoreDidImportUbiquitousContentChangesNotification that is fired by the persistent store coordinator whenever data is imported from the ubiquitous content store. Here you can merge the changes with your NSManagedObjectContext.

When the ubiquity container receives changes from iCloud, Core Data posts an NSPersistentStoreDidImportUbiquitousContentChangesNotification notification. This notification’s userInfo dictionary is structured similarly to that of an NSManagedObjectContextDidSaveNotification notification except for that it contains NSManagedObjectID instances rather than NSManagedObject instances. Therefore you can merge in changes from other peers in the same way that you merge changes from other managed object contexts. Call mergeChangesFromContextDidSaveNotification: on your managed object context, passing in the notification object posted by Core Data.

https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/UsingSQLiteStoragewithiCloud/UsingSQLiteStoragewithiCloud.html

Ahmed Onawale
  • 3,992
  • 1
  • 17
  • 21
  • it doesn't fire for me, but I have observer for it. I think this notification is fired when two devices with iCloud, and one of it changes info. A moment later another device recieves this notification and merge info, isn't it? – Dima Deplov Apr 12 '16 at 12:08
  • Yes, but the documentation said the notification is posted after records are imported from the ubiquitous content store, it didn't say only when two devices with the same iCloud account and one of it change data then the other device gets notified. – Ahmed Onawale Apr 12 '16 at 12:13
  • I didn't get this notification in the scenario I mentioned anyway, unfortunately. But, as I said I observe this notification. – Dima Deplov Apr 12 '16 at 12:23