I decided to update my project to current coding and Core Data practices, as an exercise, and I discovered a couple interesting, if minor, points.
1. Managed Object Context Uses Weak References
The whole purpose of the project was that, if I tried to fetch the same objects in two different Core Data contexts, the transient attributes wouldn’t be preserved.
But now, I found that even doing the same fetch in the same context would return different Objective-C objects, and thus would not preserve the transient attributes for any objects that I had made previously. What had changed? What was going on?
What had changed, as far as I can see, is that Core Data is far more aggressive in deleting in-memory objects that don’t have any references to them except the context. Since my original project was doing a fetch every time it wanted the list of objects, and keeping no permanent reference to them, that meant that every object except the most recent one was going away and being recreated, and thus their transient attributes were not being preserved.
I’ve changed the project to keep its own list of the objects it has created so far, so they’ll stick around until I click the “Refresh” button.
This also means that I don’t need multiple contexts. I can just nil out my own list (and call
reset on the context to be sure), and I’ll get new model object instances for my next fetch. This means that I can update my code to use the new
NSPersistentContainer class and its main-thread-only
viewContext for all my work, without worrying about maintaining multiple main-thread contexts myself.
2. There’s a Trick to Editing a Managed Object Model at Runtime
In my original project, the model was set to not use a transient attribute. If you wanted to test transient attributes for yourself, you had to go in and manually change the model file in my project, rebuild, and run it again.
This time around, I decided to do better.
So while I still left that attribute as non-transient on disk, I added some code to edit the model in memory before it is used, and tied that value to a new checkbox in the user interface. This, the comments in
NSManagedObjectModel assure me, is totally allowed and supported.
Now, if you toggle that checkbox (which deletes the current list contents), you’ll change the behavior to either use a transient name attribute (so that refreshes will nil out the names) or a non-transient name attribute (so that refreshes won’t nil out the names).
The trick? The instance of the model you load from disk can’t be edited at all, even before its use in a persistent container. You have to make a copy of it.
3. In-Memory Stores Can’t Be Transferred
My original project used an on-disk persistent data store, but deleted it every time the app started up.
This time around, instead, I used an in-memory persistent data store, which resets itself on every restart with no muss, no fuss. (This is also very useful for unit tests.)
Now above, I said that if you toggle the “Transient” checkbox, all the current database contents are deleted, right? That’s because I have to throw away the current model, and make a new one with the transient attribute handled in a different way.
If I were using an on-disk persistent store, I could just reload the contents from disk using that new model.
But since I’m using an in-memory persistent store, there’s no on-disk backup to turn to.
And the APIs that Apple provides in
NSPersistentStoreCoordinator, as far as I can see, do not allow you to detach an existing store from a coordinator and re-attach it to a new coordinator. It always assumes you can reload the store contents from a file on disk, which makes a new store object.
I’ve long believed that, even though Apple tends to say Core Data is an object management framework independent of storage mechanism, that’s just hogwash. No company I’ve ever worked at uses Core Data for anything serious without backing it with a SQLite database, and all of Core Data’s heavy-duty features are geared towards that configuration.
Here, as we can see, even their APIs favor one kind of store over another. Which is as it should be! But I wish they’d stop pretending.
I had a problem recently with Core Data transient properties, my own fault.
What I hoped they would be was full-fledged members of the database which just happened not to be saved to disk.
What they actually are is glorified instance variables. The trouble with using a custom subclass ivar is that its value is only present in the specific object you set it on. If you get a different object representing the same entity, say from another context, it won’t have that value. These can lead to lots of problems.
I wrote a sample Mac app, available on github, that demonstrates the problem. Here’s me setting the “name” property for a couple of entities:
Here’s me using the “Refresh” button to use a second managed object context to retrieve those same entities:
Names are gone! (And pointer values are different.) If I change the name attribute to not be transient, the names are preserved, even though the pointer values are still different:
I’m sliding over a lot of details here. This post by Jakob Stoklund Olesen has more information, but since it’s from 2007, it may be out of date. (It’s still the first google hit for “Core data transient properties”, however.)
Xcode’s new Core Data model file format, introduced with Xcode 4.1 in 2011, never made any waves in Cocoa development blogs that I saw.1 Which is a shame, because it’s a pretty kick-ass improvement. Instead of a binary plist format (more specifically, Cocoa objects serialized directly to disk in a binary plist format), it’s eminently readable and diffable XML.
It reminds me, for two reasons, of Mark Pilgrim’s complaint about Apple’s switch from the standard .mbox format to the (Spotlight-friendly, but undocumented) .emlx format in Mail.app in Mac OS X 10.4, which was part of his rationale (last straw?) for leaving the Mac ecosystem and moving to Linux. (See this Daring Fireball article about it, since Pilgrim’s blog posts are now only available via the Wayback Machine.)
For one, it’s because my experience with the new format involved what I remember to be an involuntary switch. The details are a little fuzzy for me now (a good reason to write these blog posts in a more timely manner), but I recall my edits being saved in the new format without my having done anything to warrant it, and without any prompting on Xcode’s part. Just like Mark’s experience in Mail. If Xcode did make a habit of that, I would be rather annoyed, and for the same reason. (As an aside, I go on about this at some length in my first podcast (link forthcoming2 due to this mistaken remembrance. See the second podcast for followup, or just read the rest of this post.) I’m a sophisticated enough user that I care about format switches, mostly for the sake of backwards compatibility. For non-technical users, losing that compatibility might be fine. Developers, however, are by their nature not non-technical users.
It turns out, I can’t reproduce that experience now with fresh reinstalls of Xcode 4.0.2, 4.1, and the latest Xcode 4.3.2. Instead, the only thing that changes the format is for you to explicitly change it in the model’s inspector pane. Which is great. Exposing that level of control over a file’s format is exactly what a developer tool should do. And they do it for other important file types too, like xibs. So, kudos.
For two, it’s because this is kind of Mark’s experience in reverse. Going from a less standard format to a more standard format. (I did say “kind of”.) While it isn’t documented, it is uncluttered, as far as I can tell, by the kind of unreadable binary garbage that pollutes other XML file formats. Entities are referred to solely by name, not UUID. Attribute titles are simple and understandable. From a practical perspective, this will make the SCM diffs you do every day far more useful and enjoyable. But I could also see someone using a script to mechanically put together a Core Data model file, much more easily than you would put together an Xcode project file. And going further, I could see someone writing a third-party Core Data modeling tool that spits out one of these. I’m almost surprised someone hasn’t done it already, though the Xcode 4 Core Data modeling tool is good enough (and free) that there probably isn’t much demand for it.