Slate was the followup to Cadmium, and it came out of one specific realization: most of the bugs I kept hitting in Core Data apps were mutation bugs. Some object you fetched on a background queue got handed off to the main thread, or held onto past its context’s lifetime, or modified outside a transaction, and now you’ve either got a crash or quietly wrong data.
The fix I landed on was to never expose NSManagedObject to the rest of the
app at all. Instead, Slate ships with a code generator (SlateGenerator) that
reads your .xcdatamodel and outputs an immutable Swift struct for each
entity. Queries return arrays of those structs, not the underlying managed
objects:
let books = try readContext[Book.self].filter("pageCount > \(pageCount)").fetch()
The books you get back are plain immutable values (Sendable before
Sendable was a keyword.) You can stash them, sort them, transform them, hand
them off across threads, and none of that matters because they cannot mutate.
Mutations happen in a separate write context, and only there. When a mutation transaction commits, listeners get notified and can re-fetch fresh snapshots. The whole shape of the framework is single-writer/multi-reader with no escape hatches, which fits cleanly into a unidirectional data flow architecture.
The trade-off is that you give up Core Data’s lazy faulting and dynamic relationship navigation. Snapshots are eagerly materialized at fetch time. That’s perfectly fine for the kind of apps I was building, but it would struggle on data sets with hundreds of thousands of objects.