Restoring Transience

While doing some Core Data research, I came across my old GitHub project (from this post) demonstrating transient attributes.

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?
Transient app window showing three rows, with two having nil name attributes, and only the third having a non-nil name

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.

Transient app window showing a new checkbox on the right labeled 'Transient'

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.