Partial Solution for iCloud & Core Data Ubiquitous Update bug
Friday, March 30, 2012 at 6:48PM Ok, I have a partial work around for the iCloud and Core Data bug.
First, a little background info. Everything here is based on my previous multi-doc iCloud example. As I mentioned in that post, there seems to be a race condition. If I have the device running on two apps, and I make a change on one app, every once in a while the second app will receive a NSPersistentStoreDidImportUbiquitousContentChangesNotification notification, but when I try to update the data, nothing happens. It seems like the notification is fired before the managed object contexts or persistent store coordinator have been properly updated.
Things seemed a little more solid with iOS 5.1. Now, at least, if I closed the document and reopened it, it would fetch the correct information. But it still wasn't perfect.
In the original project, when I received a notification, my app updated the managed object context as shown below:
// This should work, but seems to create race conditions
[moc performBlock:^{
[moc mergeChangesFromContextDidSaveNotification:note];
self.documentTitle.text = self.textEntry.title;
self.text.text = self.textEntry.text;
}];
In playing around, I discovered that if you reset both the parent context and the current managed object context, then reload your objects, you will actually get the correct value.
[moc performBlock:^{
id objectid = self.textEntry.objectID;
id parent = [moc parentContext];
[parent performBlockAndWait:^{
[parent reset];
}];
[moc reset];
self.textEntry = (TextEntry*) [moc objectWithID:objectid];
self.documentTitle.text = self.textEntry.title;
self.text.text = self.textEntry.text;
}];
Here, I'm just saving the object ID for my entry. Next, I clear both managed object contexts. Then I re-fetch the object for the ID and update my user interface. This works around the race condition, but you will lose any unsaved data in your managed object context--which is less than ideal. For the current app, this doesn't matter. But, for many applications, you may have to get the object IDs out of the notification and perform your own data merging.
In my tests, this has proven to be very reliable--depending, of course, on the quality of your internet connection. Updates occurred almost instantly when working at home. When testing it at Starbucks, I'd usually have to wait a minute or so for the update to propagate. So be patient when testing. I like to set a breakpoint in my update code that plays a sound then continues execution, just to alert me when the update is finally triggered.
However, I've now noticed another bug. If I make changes on both devices at the same time, then I can see that I trigger two updates--but neither device actually changes. They both keep their original data. This is true even if I shut down the apps and restart them. Of course, that's a pretty artificial test--so I think I'm OK ignoring that one. If you're making simultaneous changes on your devices, you kind of deserve whatever chaos you create.
Anyway, I know this is an imperfect solution, but I post it in the hope that it will inspire someone, and that we may be able to find a better solution to this problem.
Thanks,
-Rich-



Reader Comments (2)
Rich,
Perhaps the problem could also be related to performBlock: possibly being executed in a different thread? Which means you probably shouldn't be updating UI on that thread. Just trying to think of alternative solutions :)
Ivan,
performBlock:will execute in the thread that was used to create the moc. In this case, it should be called in the main thread, so the UI update calls should be fine.In any case, the notification is sometimes called before the data is actually available. That just doesn't seem right.
I think I might try listening for
NSManagedObjectContextObjectsDidChangeNotificationon both the parent and child MOC and see if and when they are called relative toNSPersistentStoreDidImportUbiquitousContentChangesNotification. Maybe there's a work around there.-Rich-