Monday, October 2, 2023

@Model for CoreData

Helge Heß:

ManagedModels is a package that provides a Swift 5.9 macro similar to the SwiftData @Model. It can generate CoreData ManagedObjectModel’s declaratively from Swift classes, w/o having to use the Xcode “CoreData Modeler”.

Unlike SwiftData it doesn’t require iOS 17+ and works directly w/ CoreData. It is not a direct API replacement, but a look-a-like.

[…]

A CoreData object has to be initialized through some very specific initializer, while a SwiftData model class must have an explicit init, but is otherwise pretty regular.

The ManagedModels @Model macro generates a set of helper inits to deal with that. But the general recommendation is to use a convenience init like so[…]

I like this general approach, but my experience is that it’s not good to cache the NSEntityDescription. If you return it from a class method, there can be problems if you use the same managed object class with multiple managed object models or multiple instances of the same model when testing. (I avoid the similar convenience methods that are built into Core Data, too.) Maybe this has since been fixed, but I found that it worked better to specify the entity by name in the fetch request so that it gets looked up in the proper managed object model.

Previously:

Update (2023-10-09): Helge Heß:

Frohlocket, a new release of my #SwiftLang Model macro for #CoreData is out. As suggested by @mjtsai entities are not cached in the type anymore and multiple MOMs can be built from them (MOMs themselves are still cached for other methods that take PersistentModel types, like .modelContainer(for: Todo.self)). And a few more bugfixes related to optionals.

9 Comments RSS · Twitter · Mastodon

Hi Michael,

Yep, I hit multiple issues with caching the entity description in a project of mine as recently as two years ago, when working with multiple documents and when performing heavyweight migrations. It's odd that these convenience methods exist, as the potential for issues is there quite quickly, and people are going to hit them eventually, and the fixes might require a big rewiring of the code to pass an NSManagedObjectContext everywhere.

@Leo I found that even NSManagedObject.init(context:) will fail, presumably because it is using the convenience method internally, even though you are directly passing it the context, which has access to the model. So I always pass the entity description directly.

Also, reusing fetch requests can cause problems—it seems to stash info in them from the model. So even if you create the fetch request without the convenience method and without reference to an entity description, that doesn’t protect you from it later referencing an entity from the wrong model. Better to create a fresh fetch request each time.

I was surprised by the entity model coupling but then figured, it probably makes sense. It is not fixed BTW, I ran over that in tests: https://github.com/Data-swift/ManagedModels/issues/20

But I'm not convinced it is an actual issue, because if the model is defined _in code_, how could there be two entities ever?

FWIW I also unique MOMs based on the input types (so that you get the same MOM if the same types are requested).

But I'm not convinced it is an actual issue, because if the model is defined _in code_, how could there be two entities ever?

It’s not that you would have two entities but that the same entity could be used in two different versions of the same model or in two different models that have entities in common.

Yes, I can see that this might be a thing if you use a model that is declared externally. But not how it can be an issue if the model is literally tied to one specific class.
E.g. for migration or versioning, you use two distinct classes (types) in SwiftData too.

SwiftData in various places deals with arrays of model classes. Does it not let you put the same class in more than one such array?

It might, but I still don't see how that would be useful as relationship resolution etc is all tied to the static type of the entity/model class.

I.e. I _think_ SwiftData actually does what you want, but I don't see how that is of any practical use. (unless you could parameterize the model construction).

Because there may be an entity (or a closure (in the graph theory sense) of entities) that is used in multiple distinct models or in different versions of the same model, where only unrelated entities varied between the models or changed between versions. No need to make an identical copy of the same class.

Leave a Comment