Skip to content

Latest commit

 

History

History
51 lines (31 loc) · 4.87 KB

core_data.md

File metadata and controls

51 lines (31 loc) · 4.87 KB

CoreData

In my conversations with developers, I’ve heard a pretty common theme from them that “Core Data is hard” or “Core Data is buggy” or “I could never get it to work right and gave up on it”.

I’ve spent a lot of time using Core Data and thought I’d share my “Laws of Core Data”. These are a set of rules I’ve developed over time on how to use Core Data in such a way that it is almost entirely painless. When I follow these rules, I almost never have any problems using it.

Examples of NSPredicate usage.

Additionally you should almost never use NSPersistentStoreCoordinator’s migratePersistentStore method but instead use the newer replacePersistentStoreAtURL. (you can replace emptiness to make a copy). The former loads the store into memory so you can do fairly radical things like write it out as a different store type. It pre-dates iOS. The latter will perform an APFS clone where possible.

Set in Swift is an immutable value type. We do not recommend making Core Data relationships typed this way despite the obvious convenience. Core Data makes heavy use of Futures, especially for relationship values. These are reference types expressed as NSSet. The concrete instance is a future subclass however. This lets us optimize memory and performance across your object graph. Declaring an accessor as Set forces an immediate copy of the entire relationship so it can be an immutable Swift Set. This loads the entire relationship up front and fulfills the Future all the time, immediately. You probably do not want that.

When Apple introduced changes to Core Data + CloudKit integration in 2019, they sold developers on a dead-simple API: add iCloud sync to your Core Data app with “as little as one line of code.” That one line, of course, is simply changing NSPersistentContainer to NSPersistentCloudKitContainer and enabling a few capabilities in the project settings. Boom, done! And in fact, Apple’s “Core Data –> Host in CloudKit” SwiftUI project template does those things for you, so you’re good to go, right?

When unit testing with Core Data I like using an in-memory store for speed and ease of clean up. But I also want, at least some of the time, to test with a disk-based store. Changing the location of the test database avoids overwriting or conflicting with any application database that I might already have installed on the simulator or device.

Apple recommends adding some launch arguments and environment variables to your Xcode schemes to catch and debug Core Data problems. I’ve known about some of these for a long time others were new to me.

The old way of creating an in-memory store was to change the store type in the persistent store descriptor before loading the store. The default is NSSQLiteStoreType but we can switch to NSInMemoryStoreType:

storeDescription.type = NSInMemoryStoreType

There’s nothing I can find in the documentation but Apple showed a different way during WWDC 2018:

storeDescription.url = URL(fileURLWithPath: "/dev/null")

This still uses an SQLite store but we keep it in memory instead of writing it to disk. As well as being faster this also gives us a clean store each time.

Testing Core Data has some challenges. Using an in-memory store helps but what if the operation you want to test happens asynchronously? One approach is to have the test listen for the notification Core Data sends when it saves changes.

Can we integrate some of SwiftData’s excellent design philosophies and ingenious implementations into the practical use of Core Data? This article aims to explore how to introduce elegant and safe concurrency operations similar to those of SwiftData into Core Data, implementing a Core Data version of @ModelActor.