{"id":18654276,"url":"https://github.com/fatbobman/coredataevolution","last_synced_at":"2026-04-01T18:38:09.662Z","repository":{"id":257825887,"uuid":"784182260","full_name":"fatbobman/CoreDataEvolution","owner":"fatbobman","description":"SwiftData-style actor isolation, Swift-first NSManagedObject macros, and typed mapping for   modern Core Data projects.","archived":false,"fork":false,"pushed_at":"2026-03-20T03:05:23.000Z","size":1403,"stargazers_count":102,"open_issues_count":0,"forks_count":5,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-03-20T05:34:04.881Z","etag":null,"topics":["core-data","coredata","nsmanagedobject","predicate","sqlite","swift","swift-concurrency","swift-macros","swiftdata"],"latest_commit_sha":null,"homepage":"https://fatbobman.com/en/posts/why-i-am-still-thinking-about-core-data-in-2026/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fatbobman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"github":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":"fatbobman","thanks_dev":null,"custom":["https://afdian.com","https://www.paypal.com/paypalme/fatbobman"]}},"created_at":"2024-04-09T10:53:49.000Z","updated_at":"2026-03-20T03:05:15.000Z","dependencies_parsed_at":"2026-02-24T03:01:13.163Z","dependency_job_id":null,"html_url":"https://github.com/fatbobman/CoreDataEvolution","commit_stats":null,"previous_names":["fatbobman/coredataevolution"],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/fatbobman/CoreDataEvolution","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatbobman%2FCoreDataEvolution","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatbobman%2FCoreDataEvolution/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatbobman%2FCoreDataEvolution/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatbobman%2FCoreDataEvolution/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fatbobman","download_url":"https://codeload.github.com/fatbobman/CoreDataEvolution/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatbobman%2FCoreDataEvolution/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290923,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["core-data","coredata","nsmanagedobject","predicate","sqlite","swift","swift-concurrency","swift-macros","swiftdata"],"created_at":"2024-11-07T07:14:32.545Z","updated_at":"2026-04-01T18:38:09.647Z","avatar_url":"https://github.com/fatbobman.png","language":"Swift","readme":"# CoreDataEvolution\n\n![Swift 6](https://img.shields.io/badge/Swift-6-orange?logo=swift) ![iOS](https://img.shields.io/badge/iOS-13.0+-green) ![macOS](https://img.shields.io/badge/macOS-10.15+-green) ![watchOS](https://img.shields.io/badge/watchOS-6.0+-green) ![visionOS](https://img.shields.io/badge/visionOS-1.0+-green) ![tvOS](https://img.shields.io/badge/tvOS-13.0+-green) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/fatbobman/CoreDataEvolution)\n\nCoreDataEvolution **does not** replace Core Data. It modernizes how Core Data is expressed, isolated, and maintained in Swift codebases.\n\nCoreDataEvolution brings four ideas together for Core Data projects:\n\n- SwiftData-style actor isolation for Core Data\n- a Swift-first source representation for `NSManagedObject` models\n- a typed mapping layer for sort and predicate code that improves naming flexibility and type\n  safety without forcing changes to the underlying model\n- tooling that keeps source declarations aligned with the real Core Data model\n\nThis document focuses on the user-facing story:\n\n- what the library is for\n- which pain points it solves\n- what the major features are\n- how the pieces fit together\n- where to go next in the detailed guides\n\n## What Problem This Library Solves\n\nCore Data is no longer the newest persistence option in Apple's ecosystem.\n\nSwiftData offers a more modern declaration style. GRDB, SQLiteData, and other approaches give many\nteams more direct database control.\n\nAnd yet Core Data is still a pragmatic choice for many production apps because it offers:\n\n- broad platform support\n- mature migration and store behavior\n- an object-graph model many teams still prefer\n- existing schemas that teams cannot easily replace\n\nSo the question this library starts from is not:\n\n- \"Should everyone still choose Core Data?\"\n\nIt is:\n\n- \"If a project is still using Core Data, how can it fit modern Swift more naturally?\"\n\nThat is where the real friction shows up today.\n\n### Pain Point 1: The model declaration layer no longer feels native to modern Swift\n\nThe problem is not that Core Data cannot model data.\n\nThe problem is that the default `NSManagedObject` source layer becomes awkward when you want\ntoday's Swift code to express intent clearly.\n\nTypical pressure points include:\n\n- better Swift-facing names than the stored schema names\n- enums instead of raw values\n- Codable payloads\n- transformable values\n- structured composition values\n- predictable generated boilerplate instead of repeated hand-written bridging\n\nWithout help, teams often end up writing a thick layer of computed properties and bridging code\njust to make the model feel natural in Swift.\n\nCoreDataEvolution adds a Swift-first declaration layer around `NSManagedObject` while keeping the\nreal Core Data runtime underneath.\n\n### Pain Point 2: The concurrency model still feels older than the rest of the codebase\n\nCore Data can absolutely be used safely with concurrency, but its default workflow still tends to\npull developers back toward `perform`, context passing, and ad hoc thread-confinement discipline.\n\nCompared with the actor-isolated style many Swift developers now expect, traditional Core Data code\noften falls back to:\n\n- manually passing contexts around\n- remembering thread confinement rules\n- reloading objects by `NSManagedObjectID`\n- building one-off background helpers\n\nCoreDataEvolution brings a SwiftData-style actor-isolated workflow to Core Data.\n\n### Pain Point 3: Naming flexibility, type safety, and schema stability pull against each other\n\nOnce a Core Data model ships, schema names often become hard to change safely.\n\nThat creates pressure to keep old persistent names while still wanting better Swift-facing names in\napplication code.\n\nThe usual result is some combination of:\n\n- hand-written mapping code\n- stringly-typed sort and predicate keys\n- growing drift between `.xcdatamodeld`, Swift source, and query code\n\nCoreDataEvolution adds a typed mapping layer for sort and predicate construction so you can improve\nSwift naming and type safety without being forced to rename the underlying schema.\n\n### Pain Point 4: Experience and convention are not enough anymore\n\nMany teams already know how to work around these issues.\n\nThe harder problem is that the workarounds often live as:\n\n- tribal knowledge\n- local conventions\n- discipline that must be remembered in code review\n\nThat gets less reliable as projects grow, teams change, and AI-assisted coding becomes part of the\nday-to-day workflow.\n\nCoreDataEvolution tries to turn those conventions into clearer APIs, generated structure, and an\noptional toolchain that can verify alignment over time.\n\nBackground article:\n\n- [Why I'm Still Thinking About Core Data in 2026](https://fatbobman.com/en/posts/why-i-am-still-thinking-about-core-data-in-2026/)\n\n## Mental Model\n\nThe project is built around one central idea:\n\n\u003e `@PersistentModel` is a source-level representation of a Core Data model, not a replacement for Core Data itself.\n\nThat distinction matters.\n\n### What `@PersistentModel` is\n\nIt is a Swift-facing declaration layer for:\n\n- attributes\n- relationships\n- composition values\n- generated typed path metadata\n- generated runtime schema metadata for test/debug use\n\n### What `@PersistentModel` is not\n\nIt is not:\n\n- a replacement for `.xcdatamodeld` in production\n- a migration system\n- a different persistence engine\n- a runtime reflection layer\n\nThe production source of truth is still your Core Data model.\n\nThe macro layer gives you a better, more explicit, more toolable representation of that model in Swift source.\n\nThis is the most important mental model for new users:\n\n- keep building the real schema in Xcode\n- use `@PersistentModel` to describe that schema in Swift\n- use `cde-tool` (optional) to keep the two layers aligned\n\n## The Three Main Parts of the Package\n\n### 1. Actor isolation for Core Data\n\nUse:\n\n- `@NSModelActor`\n- `@NSMainModelActor`\n\nThese macros generate the boilerplate needed to safely work with Core Data through actor isolation or main-actor isolation.\n\nGood fit:\n\n- background write handlers\n- UI-facing main-thread coordinators\n- tests that need explicit isolation boundaries\n\nGuide:\n\n- [Docs/NSModelActorGuide.md](./Docs/NSModelActorGuide.md)\n\nBackground article:\n\n- [Core Data Reform: Achieving Elegant Concurrency Operations Like SwiftData](https://fatbobman.com/en/posts/core-data-reform-achieving-elegant-concurrency-operations-like-swiftdata/)\n\n### 2. `@PersistentModel` and related macros\n\nUse:\n\n- `@PersistentModel`\n- `@Attribute`\n- `@Relationship`\n- `@Ignore`\n- `@Composition`\n- `@CompositionField`\n\nThis is the model declaration layer.\n\nIt gives you:\n\n- explicit attribute/relationship metadata\n- generated Core Data accessors\n- lightweight `relationshipNameCount` accessors for to-many relationships\n- generated to-many relationship helper APIs\n- typed key/path metadata for sort and predicate construction\n- runtime schema metadata for tests and debug workflows\n\nGuide:\n\n- [Docs/PersistentModelGuide.md](./Docs/PersistentModelGuide.md)\n\n### 3. `cde-tool`\n\nUse `cde-tool` when you want a repeatable model-to-source workflow.\n\nIt is intentionally optional.\n\nThe core value of CoreDataEvolution lives in the actor-isolation macros and the macro-based model\ndeclaration layer. You can use those directly without adopting the tool at all.\n\n`cde-tool` exists as an extra layer for teams that want stronger workflow guarantees, especially\nfor CI/CD, drift detection, and existing-project migration.\n\nIt helps with:\n\n- generating `@PersistentModel` source from an existing Core Data model\n- validating drift between `.xcdatamodeld` and source declarations\n- inspecting the resolved schema view used by the toolchain\n- applying safe autofix for deterministic issues\n\nThat first point is especially useful when adopting the package in an existing Core Data project:\nthe tool can quickly turn a legacy `.xcdatamodeld` into a usable `@PersistentModel` starting point,\nsimilar in spirit to Xcode's model code generation, but aligned with CoreDataEvolution's macro\nlayer.\n\nGuide:\n\n- [Docs/CDEToolGuide.md](./Docs/CDEToolGuide.md)\n\n## Core Features\n\n### SwiftData-style actor isolation for Core Data\n\n```swift\nimport CoreDataEvolution\n\n@NSModelActor\nactor ItemStore {\n  func renameItem(id: NSManagedObjectID, to newTitle: String) throws {\n    guard let item = self[id, as: Item.self] else { return }\n    item.title = newTitle\n    try modelContext.save()\n  }\n}\n```\n\nThis lets you keep Core Data while moving to a much cleaner isolation model.\n\n### Swift-first model declarations on top of `NSManagedObject`\n\n```swift\n@objc(Item)\n@PersistentModel\nfinal class Item: NSManagedObject {\n  @Attribute(persistentName: \"name\")\n  var title: String = \"\"\n\n  @Relationship(inverse: \"items\", deleteRule: .nullify)\n  var tag: Tag?\n}\n```\n\nThis is the most important thing to understand:\n\n- the source is Swift-first\n- the runtime is still Core Data\n- the model file is still part of the system\n\nFor relationships:\n\n- to-one properties generate a getter and setter\n- to-many properties (`Set\u003cT\u003e` / `[T]`) generate a getter only\n- mutate to-many relationships through generated helpers such as `addToTags`,\n  `removeFromTags`, and `insertIntoOrderedTags(_:at:)`\n\n### Typed key/path mapping for sort and predicate code\n\nThis is one of the library's distinctive features.\n\nWhen 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.\n\nThat means you can write:\n\n```swift\nlet sort = try NSSortDescriptor(\n  Item.self,\n  path: Item.path.title,\n  order: .asc\n)\n\nlet predicate = NSPredicate(\n  format: \"%K == %@\",\n  Item.path.title.raw,\n  \"hello\"\n)\n```\n\nwhile the store still uses the original field name.\n\nGuide:\n\n- [Docs/TypedPathGuide.md](./Docs/TypedPathGuide.md)\n\n### Explicit storage strategy selection\n\nThe source layer makes storage strategy explicit instead of burying it in hand-written bridging code.\n\nSupported source-level storage choices include:\n\n- `.default`\n- `.raw`\n- `.codable`\n- `.transformed(...)`\n- `.composition`\n\nGuide:\n\n- [Docs/StorageMethodGuide.md](./Docs/StorageMethodGuide.md)\n\n## Important Preconditions\n\nThese are the points that new users most often need clarified.\n\n### `@PersistentModel` works with Core Data models\n\nFor production use, `@PersistentModel` is not a replacement for `.xcdatamodeld`.\n\nYou still build and maintain a Core Data model.\n\nThe macro layer is the source representation that sits on top of it.\n\nThat means:\n\n- you still need a Core Data source model for production workflows\n- `cde-tool` reads that model and helps generate/validate the Swift declaration layer\n- the macro-generated runtime schema is for test/debug scenarios, not for replacing the production model system\n- unsupported runtime primitive types fail generation instead of silently downgrading schema\n\n### Relationship `inverse` uses the persistent relationship name\n\nThis is easy to miss.\n\nIn:\n\n```swift\n@Relationship(\n  persistentName: \"primary_category\",\n  inverse: \"items\",\n  deleteRule: .nullify\n)\nvar category: Category?\n```\n\n`inverse` refers to the relationship name in the Core Data model on the other side.\n\nIt does **not** refer to the other Swift property name.\n\n### `composition` requires Core Data composite attribute support\n\n`@Composition` maps to Core Data composite attributes.\n\nFor schema-backed models, this means the Xcode model must declare a real top-level `Composite`\nattribute, not a pair of flattened entity fields and not a `Transformable` fallback.\n\nThat means it requires platform support for that Core Data feature:\n\n- iOS 17+\n- macOS 14+\n- tvOS 17+\n- watchOS 10+\n- visionOS 1+\n\nIf your deployment target is below those versions, do not use `composition`.\n\n## Runtime Schema for Tests and Debugging\n\nThe package also supports a pure Swift runtime-schema path for tests and debugging.\n\nExample:\n\n```swift\nlet model = try NSManagedObjectModel.makeRuntimeModel(Item.self, Tag.self)\n\nlet container = try NSPersistentContainer.makeRuntimeTest(\n  modelTypes: Item.self, Tag.self\n)\n```\n\nThis path is intentionally limited.\n\nIt is useful when you want:\n\n- test-only model construction\n- debug-only schema checks\n- non-Xcode workflows for tests\n\nIt is not intended to replace `.xcdatamodeld` in production.\n\n## `cde-tool` Workflow\n\nTypical workflow:\n\n1. start from a Core Data source model\n2. create a config\n3. generate `@PersistentModel` source\n4. add hand-written methods and computed properties in separate extension files\n5. validate drift over time\n\nTypical first setup:\n\n```bash\ncde-tool bootstrap-config \\\n  --model-path Models/AppModel.xcdatamodeld \\\n  --output cde-tool.json\n```\n\nThen:\n\n```bash\ncde-tool generate --config cde-tool.json\ncde-tool validate --config cde-tool.json\n```\n\n### Validation modes\n\n`cde-tool validate` supports two mental models:\n\n- `conformance`\n  - checks whether source follows the rules and matches the schema logically\n- `exact`\n  - additionally requires tool-managed files to match current generated output exactly\n\n`exact` is intentionally stricter and should not be the default workflow for every team.\n\nIf you use `exact`, keep these rules in mind:\n\n- do not hand-edit tool-managed files\n- do not let format/lint rewrite tool-managed files\n- add custom methods and computed properties in separate extension files\n\n## Recommended Project Structure\n\nA practical structure looks like this:\n\n- Core Data model in `Models/`\n- generated source in a dedicated generated folder\n- hand-written extensions in separate files\n- `cde-tool.json` checked into the repository\n\nExample:\n\n- `Models/AppModel.xcdatamodeld`\n- `Sources/AppModels/Generated/`\n- `Sources/AppModels/Item+Extras.swift`\n- `cde-tool.json`\n\nThis keeps generated and hand-written code clearly separated.\n\n## Where to Read Next\n\nStart here based on what you want to do.\n\n### If you want actor-isolated Core Data code\n\n- [Docs/NSModelActorGuide.md](./Docs/NSModelActorGuide.md)\n- [Core Data Reform: Achieving Elegant Concurrency Operations Like SwiftData](https://fatbobman.com/en/posts/core-data-reform-achieving-elegant-concurrency-operations-like-swiftdata/)\n\n### If you want Swift-first Core Data model declarations\n\n- [Docs/PersistentModelGuide.md](./Docs/PersistentModelGuide.md)\n\n### If you want remapped fields to still work in sort and predicate code\n\n- [Docs/TypedPathGuide.md](./Docs/TypedPathGuide.md)\n\n### If you want to choose the right storage strategy\n\n- [Docs/StorageMethodGuide.md](./Docs/StorageMethodGuide.md)\n\n### If you want the CLI workflow\n\n- [Docs/CDEToolGuide.md](./Docs/CDEToolGuide.md)\n\n## Summary\n\nCoreDataEvolution is not trying to replace Core Data.\n\nIt is trying to make Core Data easier to use in modern Swift codebases by adding:\n\n- better isolation patterns\n- better model source declarations\n- better schema-to-source tooling\n- better typed mapping for renamed fields\n\nIf your project still relies on Core Data, but you want a source model and workflow that feel much\ncloser to modern Swift, that is the space this library is designed for.\n\n## System Requirements\n\n- iOS 13.0+ / macOS 10.15+ / watchOS 6.0+ / visionOS 1.0+ / tvOS 13.0+\n- Swift 6.0\n\nNote: The custom executor uses a compatible `UnownedJob` serial-executor path to support the minimum deployment targets.\n\n## Contributing\n\nWe welcome contributions! Whether you want to report issues, propose new features, or contribute to the code, feel free to open issues or pull requests on the GitHub repository.\n\n## License\n\nCoreDataEvolution is available under the MIT license. See the LICENSE file for more information.\n\n## Acknowledgments\n\nSpecial thanks to the Swift community for their continuous support and contributions.\nThanks to [@rnine](https://github.com/rnine) for sharing and validating the iOS 13+ compatibility approach that inspired this adaptation.\n\n## Support the project\n\n- [🎉 Subscribe to Fatbobman's Swift Weekly](https://weekly.fatbobman.com)\n- [☕️ Buy Me A Coffee](https://buymeacoffee.com/fatbobman)\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=fatbobman/CoreDataEvolution\u0026type=Date)](https://star-history.com/#fatbobman/CoreDataEvolution\u0026Date)\n","funding_links":["https://buymeacoffee.com/fatbobman","https://afdian.com","https://www.paypal.com/paypalme/fatbobman"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffatbobman%2Fcoredataevolution","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffatbobman%2Fcoredataevolution","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffatbobman%2Fcoredataevolution/lists"}