WOODY'S
FINDINGS

CoreData: the burden of the past

Tip

6 February 2022

I have been using CoreData for a few years now, and despite the lack of official documentation1, I have found it to be a very reliable solution to persist data on mobile and desktop.

The framework has evolved with new Swift features like the structured concurrency and the new Apple frameworks like Combine. But I think there is one place where CoreData has not moved - at all. And that’s the way we interact with the data model.

XML, really?

When I started using CoreData, I was pleased to have a GUI to help me define my data model. But it did not last long before I stumbled upon one of the major problem of the GUI approach: it does not fit in the Swift world. I am really unpleased to have to use XML to define my entities and their relationships when we have ResultBuilder to declare a SwiftUI view or property wrappers to declare a command-line tool arguments. Even a simpler solution like the way we declare a Swift package would be better than XML.

Sure the GUI hides the XML from you, but it sometimes makes it worst. At the KaraFun Group we decided to move from Realm to CoreData for features like the Combine integration or more generally the « context » approach which I find very efficient. Thus, we had to create all the entities of our model. I stopped counting the times where Xcode crashed while loosing the modifications on the XML file. I started to find it more convenient to edit the XML file with a standard IDE.
And when the GUI does not crash, I can’t say it works well. Most often you have to unselect an attribute to be able to edit it in the right panel. The UML visual representation of the entities and their relationship would be nice to use though if the Xcode was not constantly moving them all back in the same place, forcing you to drag and drop all of your entities away from each other.
Finally I wonder how the new Swift Playgrounds app that allows to build an app with an iPad would offer this data model editing. I can't imagine the Xcode editor on iPad.

I often dream about a declarative way to build the data model. Sometimes I want to start implementing one in an open-source library but then I think about all the maintenance this would require to stay up to date with the latest OS versions and I realize that it would require too much work for a single person on their free time. And I am not very good at gathering people to help me build open-source libraries.
(I know that some projects like CoreStore exist and are used by many people. But I am not a great fan of the API it provides to build a data model, like the property wrappers extending on more than 5 lines).

What about fetching?

A few months ago I was more reckless and I was not thinking about all the work required to build a solution for an Apple framework while not being at Apple. I implemented the library CoreDataCandy to offer simple conversion logic from a stored value to a more advanced one (like RawValue <=> RawRepresentable). It worked fine but I was undecided for the fetching part: should I provide functions to fetch the raw value of a type, or the type directly? And should I minimise the effort to specify the entity type?

That’s an issue I addressed very recently with SafeFetching. I built a library that focuses on safely building NSFetchRequest that can then be used with a NSManagedObjectContext.
To give you an idea, here is an example with an imaginary User entity:

User.request()
    .all(after: 10)
    .where(\.score >= 15 || \.name != "Joe")
    .sorted(by: .ascending(\.score), .descending(\.name))
    .nsValue // NSFetchedRequest<User>

The main feature is the where function which allows to specify a predicate in a « Swift way » with key paths and type checking. For instance, it’s not possible to write something like \.name == 10 since the name property is a String. Some advanced operators allow to go even further while requiring to use the special * operator:

User.request()
	 .first()
    .where(\.score * .isIn(10...100) && \.name * .hasPrefix("Jo"))
    .nsValue

I am quite happy with the results. This was not easy to allow the key path specification without the root type, but a solution exits in Swift for that.
Unfortunately, something is missing in Swift to really make this library « safe »: using key paths for the attributes only work on objc.

If the User has, let’s say, a computed property name isAdmin like so

extension User {
	var isAdmin { true }
}

Specifying the key path will be valid for the compiler, and this would be accepted

User.request()
    .where(\.isAdmin == true)
    .nsValue

However it would crash at runtime. To transform a KeyPath to a string to be used in a NSPredicate, the only solution I found is the call to NSExpresssion(forKeyPath:) and getting the keyPath property. This function only works when the key path targets an @objc property, which is fine when you want to use a @NSManaged property. But I could not find a way to constraint the KeyPath.Value to be marked as @objc - thus preventing runtime crashes.

More importantly, if you use tools like SwiftGen to generate you CoreData entities (for, let’s say, avoid exposing the attributes setters publicly), SafeFetching is defeated with key paths. You might have the script writing the RawRepresentable conversion for you. This is a great feature since it removes the need to repeat this logic in your code. But the generated property will certainly not be compatible with Objective-C and cannot be marked as @objc. I found a workaround by declaring a StringKeyPath struct which holds a string as well a the entity and attribute types. But I am not very satisfied with it.

Of course I am not the only one (and certainly not the first) who thought about that. Using key paths to build predicate seems logical today, and would be great too. Meawnhile if you read this article and have an idea to fix the key path and @objc matter, please let me know.

As a last thought, when taking a look at the way NSFetchRequest takes a NSPredicate and is then provided to a SwiftUI view, this is almost as if two worlds were glued together. And to be frank, I don’t find this glue to be very pretty.

Wishlist to SantApple

I have watched almost all the the previous years WWDC sessions dealing with CoreData. And I am really thankful for the way the team behind CoreData keeps pushing it further while trying to remove the hassle to create and manage a stack.
Now, if it’s possible to ask for features, here is what I would like to get for the next WWDC:

  • the possibility to declare a data model by using ResultBuilder or property wrappers while defining the entities of the model
  • a safe « Swift » way to declare predicates while keeping the type (no #keyPath() that is)

Thank you for reading!

1 Fortunately some very good books can be found like Practical CoreData by Donny Wals or CoreData by tutorials from Ray Wenderlich