SkillAgentSearch skills...

CoreDataEvolution

SwiftData-style actor isolation, Swift-first NSManagedObject macros, and typed mapping for modern Core Data projects.

Install / Use

/learn @fatbobman/CoreDataEvolution
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

CoreDataEvolution

Swift 6 iOS macOS watchOS visionOS tvOS License: MIT Ask DeepWiki

CoreDataEvolution does not replace Core Data. It modernizes how Core Data is expressed, isolated, and maintained in Swift codebases.

CoreDataEvolution brings four ideas together for Core Data projects:

  • SwiftData-style actor isolation for Core Data
  • a Swift-first source representation for NSManagedObject models
  • a typed mapping layer for sort and predicate code that improves naming flexibility and type safety without forcing changes to the underlying model
  • tooling that keeps source declarations aligned with the real Core Data model

This document focuses on the user-facing story:

  • what the library is for
  • which pain points it solves
  • what the major features are
  • how the pieces fit together
  • where to go next in the detailed guides

What Problem This Library Solves

Core Data is no longer the newest persistence option in Apple's ecosystem.

SwiftData offers a more modern declaration style. GRDB, SQLiteData, and other approaches give many teams more direct database control.

And yet Core Data is still a pragmatic choice for many production apps because it offers:

  • broad platform support
  • mature migration and store behavior
  • an object-graph model many teams still prefer
  • existing schemas that teams cannot easily replace

So the question this library starts from is not:

  • "Should everyone still choose Core Data?"

It is:

  • "If a project is still using Core Data, how can it fit modern Swift more naturally?"

That is where the real friction shows up today.

Pain Point 1: The model declaration layer no longer feels native to modern Swift

The problem is not that Core Data cannot model data.

The problem is that the default NSManagedObject source layer becomes awkward when you want today's Swift code to express intent clearly.

Typical pressure points include:

  • better Swift-facing names than the stored schema names
  • enums instead of raw values
  • Codable payloads
  • transformable values
  • structured composition values
  • predictable generated boilerplate instead of repeated hand-written bridging

Without help, teams often end up writing a thick layer of computed properties and bridging code just to make the model feel natural in Swift.

CoreDataEvolution adds a Swift-first declaration layer around NSManagedObject while keeping the real Core Data runtime underneath.

Pain Point 2: The concurrency model still feels older than the rest of the codebase

Core Data can absolutely be used safely with concurrency, but its default workflow still tends to pull developers back toward perform, context passing, and ad hoc thread-confinement discipline.

Compared with the actor-isolated style many Swift developers now expect, traditional Core Data code often falls back to:

  • manually passing contexts around
  • remembering thread confinement rules
  • reloading objects by NSManagedObjectID
  • building one-off background helpers

CoreDataEvolution brings a SwiftData-style actor-isolated workflow to Core Data.

Pain Point 3: Naming flexibility, type safety, and schema stability pull against each other

Once a Core Data model ships, schema names often become hard to change safely.

That creates pressure to keep old persistent names while still wanting better Swift-facing names in application code.

The usual result is some combination of:

  • hand-written mapping code
  • stringly-typed sort and predicate keys
  • growing drift between .xcdatamodeld, Swift source, and query code

CoreDataEvolution adds a typed mapping layer for sort and predicate construction so you can improve Swift naming and type safety without being forced to rename the underlying schema.

Pain Point 4: Experience and convention are not enough anymore

Many teams already know how to work around these issues.

The harder problem is that the workarounds often live as:

  • tribal knowledge
  • local conventions
  • discipline that must be remembered in code review

That gets less reliable as projects grow, teams change, and AI-assisted coding becomes part of the day-to-day workflow.

CoreDataEvolution tries to turn those conventions into clearer APIs, generated structure, and an optional toolchain that can verify alignment over time.

Background article:

Mental Model

The project is built around one central idea:

@PersistentModel is a source-level representation of a Core Data model, not a replacement for Core Data itself.

That distinction matters.

What @PersistentModel is

It is a Swift-facing declaration layer for:

  • attributes
  • relationships
  • composition values
  • generated typed path metadata
  • generated runtime schema metadata for test/debug use

What @PersistentModel is not

It is not:

  • a replacement for .xcdatamodeld in production
  • a migration system
  • a different persistence engine
  • a runtime reflection layer

The production source of truth is still your Core Data model.

The macro layer gives you a better, more explicit, more toolable representation of that model in Swift source.

This is the most important mental model for new users:

  • keep building the real schema in Xcode
  • use @PersistentModel to describe that schema in Swift
  • use cde-tool (optional) to keep the two layers aligned

The Three Main Parts of the Package

1. Actor isolation for Core Data

Use:

  • @NSModelActor
  • @NSMainModelActor

These macros generate the boilerplate needed to safely work with Core Data through actor isolation or main-actor isolation.

Good fit:

  • background write handlers
  • UI-facing main-thread coordinators
  • tests that need explicit isolation boundaries

Guide:

Background article:

2. @PersistentModel and related macros

Use:

  • @PersistentModel
  • @Attribute
  • @Relationship
  • @Ignore
  • @Composition
  • @CompositionField

This is the model declaration layer.

It gives you:

  • explicit attribute/relationship metadata
  • generated Core Data accessors
  • lightweight relationshipNameCount accessors for to-many relationships
  • generated to-many relationship helper APIs
  • typed key/path metadata for sort and predicate construction
  • runtime schema metadata for tests and debug workflows

Guide:

3. cde-tool

Use cde-tool when you want a repeatable model-to-source workflow.

It is intentionally optional.

The core value of CoreDataEvolution lives in the actor-isolation macros and the macro-based model declaration layer. You can use those directly without adopting the tool at all.

cde-tool exists as an extra layer for teams that want stronger workflow guarantees, especially for CI/CD, drift detection, and existing-project migration.

It helps with:

  • generating @PersistentModel source from an existing Core Data model
  • validating drift between .xcdatamodeld and source declarations
  • inspecting the resolved schema view used by the toolchain
  • applying safe autofix for deterministic issues

That first point is especially useful when adopting the package in an existing Core Data project: the tool can quickly turn a legacy .xcdatamodeld into a usable @PersistentModel starting point, similar in spirit to Xcode's model code generation, but aligned with CoreDataEvolution's macro layer.

Guide:

Core Features

SwiftData-style actor isolation for Core Data

import CoreDataEvolution

@NSModelActor
actor ItemStore {
  func renameItem(id: NSManagedObjectID, to newTitle: String) throws {
    guard let item = self[id, as: Item.self] else { return }
    item.title = newTitle
    try modelContext.save()
  }
}

This lets you keep Core Data while moving to a much cleaner isolation model.

Swift-first model declarations on top of NSManagedObject

@objc(Item)
@PersistentModel
final class Item: NSManagedObject {
  @Attribute(persistentName: "name")
  var title: String = ""

  @Relationship(inverse: "items", deleteRule: .nullify)
  var tag: Tag?
}

This is the most important thing to understand:

  • the source is Swift-first
  • the runtime is still Core Data
  • the model file is still part of the system

For relationships:

  • to-one properties generate a getter and setter
  • to-many properties (Set<T> / [T]) generate a getter only
  • mutate to-many relationships through generated helpers such as addToTags, removeFromTags, and insertIntoOrderedTags(_:at:)

Typed key/path mapping for sort and predicate code

This is one of the library's distinctive features.

When a Swift-facing property name differs from the stored schema name, the macro-generated typed path layer still resolves sort and predicate keys to the correct persistent field path.

That means you can write:

let sort = try NSSortDescriptor(
  Item.self,
  path: Item.path.title,
  order: .asc
)

let predicate = NSPredicate(
  format: "%K == %@",
  Item.path.title.raw,
  "hello"
)

while the store still uses the original field name.

Guide:

Explicit storage strategy selec

Related Skills

View on GitHub
GitHub Stars112
CategoryData
Updated24m ago
Forks5

Languages

Swift

Security Score

100/100

Audited on Mar 31, 2026

No findings