Rich's Mad Rants
Powered by Squarespace

Entries in Core Data (3)


Partial Solution for iCloud & Core Data Ubiquitous Update bug

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.




Debug settings for Core Data and iCloud

I'm still trying to get Core Data and iCloud to work better together. Things seem more stable under iOS 5.1, but I'm still having an issue with a device occasionally receiving update notifications, but the new data doesn't appear to be available yet. However, when I shutdown and relaunch the application, it's then in the correct state (so, that's at least something).

I found an interesting tool for generating tons of debug information while working with iCloud and Core Data (thanks to @drewmccormack and @dlpasco on twitter). Just set the 3 launch argument.

In Xcode 4, click on the scheme and select Edit Scheme… in the drop down menu.




Now, select the Run action, and the Arguments tab. Then add " 3" as an argument.




Next time you run the app, it will generate a ton of data. Too much data, actually. I'm still trying to work through it in my own debugging. A lot of it doesn't make sense yet, but there are some interesting bits there. Like the following breakdown of all the files in my ubiquity container:


2012-03-28 16:40:29.428 MultiDocument[6616:3317] -[PFUbiquityImportScanOperation main](144): CoreData: Ubiquity:  <PFUbiquityImportScanOperation: 0x2715c0> got subpaths of root location: (

































































































And that just scratches the surface. So far, I haven't found anything that helps with my race condition problem. However, let me know if you use this setting, and if you find any useful signals inside all the noise.



Syncing multiple Core Data documents using iCloud

In "Creating iOS 5 Apps", chapter 7 demonstrates how to sync a single-document Core Data application using iCloud. As the name suggests, single-document apps (also called library apps) use a single Core Data persistent store to manage all of the applications data. However, the question came up, how can you modify this for a multi-document application--an application that used a separate Core Data persistent store for each document. I have some sample code that seems to (mostly) work. It needs some heavier testing, and there are some wrinkles that need to be worked out. So take a look and let me know if you have any suggestions.

Here are the key changes:

In the single-document app, we always knew that we had one and only one copy of the persistent store. This let us greatly simplify the design. If the persistent store didn't exist, we created a new copy in our application's sandbox. If iCloud is enabled, we set the NSPersistentStoreUbiquitousContentNameKey and NSPersistentStoreUbiquitousContentURLKey in the store's options, and we're good to go.

In the multi-doc app, we don't know if we have any documents, or how many documents we may have. This means we must search for our documents using NSMetadataQuery. I've shown using NSMetadataQuery in Chapter 6, but this time there are two important differences. First, we want to continue to look for new files after the search's initial gathering phase completes. This means we leave the Query running. We simply disable it when we want to iterate over the results, then re-enable it.

Second, we cannot search for the file name given to UIManagedDocument, since this creates a directory with the given name. Instead, we must search for a file called DocumentMetadata.plist. The directory containing that file will be the URL we use to create our UIManagedDocument. The value from DocumentMetadata.plist's NSPersistentStoreUbiquitousContentNameKey will be the value we use for the UIManagedDocument's persistentStoreOption key.

This also means we cannot simply create local copies of our UIManagedDocument anymore. We must move the document into the iCloud container--otherwise the DocumentMetadata.plist won't show up in our queries. Note that iCloud still only syncs the transaction logs, and the database itself is automatically marked as .nosync.

Sample Code

I've created sample code that shows how this works. The Core Data model is pretty pathetic. I have a single entity with the text, title and modification date. Each document should never have more than 1 entity. Which, actually, makes this a pretty poor choice for Core Data. Still, it demonstrates the key points.

I'm also using the same URL for the UIManagedDocument and the NSPersistentStoreUbiquitousContentURLKey. This will place both my persistent store and my transaction logs in the same directory. Note that, you could create separate URLs for both (as long as they're both subdirectories inside the iCloud container), but this simplifies the code somewhat. Remember, if you delete the document you also need to delete all the transaction logs.

When you launch the app, you get a list of files. Select a file to open it. You can modify either the title (in the text field) or the text (in the text view below the text field). These changes aren't saved until you exit the file (navigating back to the file list). At that point, the changes are saved to the persistent store, and then synced through iCloud. Tapping the + icon in the file list will create an empty file.

If you run the app on two devices, you can watch the synchronization occur. In the file view, add a new file to one device. It will soon appear on the second device. Open the same file on both devices. Modify the text on one. Close and re-open the file to force a save. The changes should soon appear on the other device (within a few seconds).

Bugs (or features)

First, if you watch the console, you'll sometimes see errors when the app tries to receive updates.

2011-12-18 19:32:33.443 MultiDocument[6584:3f37] +[PFUbiquityTransactionLog loadPlistAtLocation:withError:](324): CoreData: Ubiquity:  Encountered an error trying to open the log file at the location: <PFUbiquityLocation: 0x1ee6c0>: /private/var/mobile/Library/Mobile Documents/WNZF6NN7ZY~com~freelancemadscience~MultiDocument/mobile.D07EBC6E-03EA-5295-8D2F-0C1D2E737524/Test Document 1/WdKZGuhOiADrlyspq5GroEkGHfNbxImTR1BYSTCku1A=/08F64E33-39FC-4AFE-A872-A21C3282CDBD.1.cdt

Error: Error Domain=NSCocoaErrorDomain Code=256 "The operation couldn’t be completed. (Cocoa error 256 - The item failed to download.)" UserInfo=0x119240 {NSURL=file://localhost/private/var/mobile/Library/Mobile%20Documents/WNZF6NN7ZY~com~freelancemadscience~MultiDocument/mobile.D07EBC6E-03EA-5295-8D2F-0C1D2E737524/Test%20Document%201/WdKZGuhOiADrlyspq5GroEkGHfNbxImTR1BYSTCku1A=/08F64E33-39FC-4AFE-A872-A21C3282CDBD.1.cdt, NSDescription=The item failed to download.}

It looks like iCloud will try again, and these seem to resolve themselves (thought they can take a while). Very rarely, things seem to get really bad--to the point where iCloud is almost unusable. Most of the time, however, you can run the app without a hitch. I think it must be a glitch within iCloud, but I can't be sure.

Another bug often shows up when I start trying to pass changes back and forth between devices that both have the same file open. The first sync always seems to work--and it seems to work pretty well as long as each subsequent sync is in the same direction. But, when I go back and forth, eventually one of the devices will receive an update notification, but doesn't receive any new data.   Usually closing and reopening the file successfully updates the data.

I think there may be a race condition somewhere, and the notification arrives before the persistent store is really ready. I've tried delaying my response to the notification by 0.25 seconds, and in the next test, it took almost a dozen syncs before I triggered the bug. So that might help, but it doesn't fix the problem.

I've also tried clearing both the child and parent managed object context, then re-fetching the object, just to make sure I went all the way back to the persistent store, and I still get the old data--not the update. I've reported a bug about this--though I'm not sure if it is really a bug in iCloud or a problem in my code. If anyone has any suggestions, please let me know.

Also, the syncing problems seem much worse if you create a file on one device then open it as soon as it appears on the other. I'm not sure why this is. Perhaps an important file hasn't synced over yet, and things start out in a bad state. Syncing seems much more reliable if I launch one device. Create a document. Modify the document and save it. Then launch the second device, and open the file there. Again, it probably needs additional testing, and any suggestions would be greatly appreciated.