In Apple’s persistence landscape, Core Data is no longer the newest name on the shelf. Over the past few years, SwiftData has brought a more modern declarative experience; frameworks like GRDB and SQLiteData have led more developers to reconsider how relational databases fit into Swift projects. Standing here in 2026 and looking back, Core Data is neither new nor lightweight — and in many ways, its code-level story has fallen noticeably behind modern Swift.
But that doesn’t mean it has lost its value: if we still choose Core Data, can we let it continue to exist in a way that fits modern Swift?
This article isn’t a defense of Core Data, nor an attempt to convince new developers to come back to it. It’s more of a problem statement: why do people still stick with Core Data in 2026, and if they do, what are the real problems we need to solve today?
Why People Still Use Core Data in 2026
Core Data has been with us for 21 years. In that time, Apple’s developer ecosystem has gone through several major transitions: from Objective-C to Swift, from UIKit to SwiftUI, from GCD to the modern concurrency model. Even years after SwiftData launched, a significant number of developers continue to use Core Data, and the volume of existing projects built on it remains enormous.
The developers who continue to use Core Data generally fall into one of a few camps:
- They know Core Data well enough that it doesn’t feel “unusable” today
- They value certain mature and critical capabilities in Core Data over what SwiftData currently offers
- Even though GRDB or SQLiteData provide stronger performance or more direct database control, they prefer Core Data’s object graph design philosophy
- Their existing projects are built on Core Data, and the cost of migrating to another framework is too high
From a purely “can it still work?” standpoint, Core Data absolutely still works, and for many projects it remains a pragmatic choice.
The problem is that it increasingly feels like it doesn’t belong in a modern Swift project.
Using Core Data in a modern Swift/SwiftUI codebase often comes with a persistent sense of mismatch. It’s not that it can’t work — it’s that its code-level expression, concurrency model, and collaborative patterns have become harder and harder to integrate naturally with the modern code around it.
So for those who are still using Core Data, the real question isn’t “should we abandon it immediately?” It’s:
How do we bring Core Data up to speed with the modernization that has already happened in the rest of the project?
Where Core Data and Modern Swift Projects Diverge
If we want to think about where Core Data could evolve, we should first identify where the most significant mismatches actually are.
1. The model layer’s expressiveness has fallen behind modern Swift
Unlike SwiftData and SQLiteData, which are built around code-first declarations, Core Data’s mainstream modeling workflow is still Xcode’s model editor.
But what really disconnects Core Data from modern Swift projects isn’t just “the model lives in an editor.” It’s that the Swift-level model code is expressively behind.
Because Core Data’s internals trace back to Objective-C, its default model declarations in Swift have always carried a heavy legacy imprint. Many things that feel natural in Swift today simply don’t hold up cleanly on NSManagedObject:
nildoesn’t always map clearly tonullin the underlying data modelDouble/Floatvalues are hard to express as Optionals in an intuitive way- There’s no natural way to expose richer Swift types directly as properties
- Improving any of this typically requires hand-written bridging, computed properties, and extra boilerplate
These workarounds can solve isolated problems, but they come at a cost: the model declaration layer quickly becomes noisy. To get more natural Swift expressions, developers end up writing large amounts of business-unrelated bridging code — and that code in turn erodes the most valuable part of Core Data’s experience: the clarity and directness that an object graph is supposed to provide.
In other words, the problem with Core Data today isn’t that it “can’t model data.” It’s that its Swift-layer model code has become increasingly unable to express intent naturally.
2. The concurrency experience is still stuck in the GCD era
NSManagedObject in Core Data is not thread-safe. As projects grow, putting all CRUD operations on the main thread is clearly unsustainable.
Although NSManagedObjectContext has been annotated as Sendable starting with Xcode 26, this doesn’t change how Core Data is actually used for concurrency. To ensure thread safety, developers still need to wrap nearly all operations in perform.
This creates a clear generational mismatch with the way the Swift world thinks about concurrency today.
Compared to SwiftData’s @ModelActor, Core Data’s concurrency code typically suffers from several issues:
- It’s easy to accidentally write thread-unsafe code
- Nested
performclosures are unintuitive and mentally taxing - It’s hard to establish consistent, clear, reusable isolation boundaries
- This burden spills into test code, making developers less likely to write systematic concurrency tests
So the problem here isn’t that Core Data “can’t do concurrency.” It’s that its concurrency model is a poor fit for how Swift developers work today.
3. The tension between flexibility, type safety, and schema drift keeps growing
Core Data has traditionally given developers a great deal of flexibility in queries and sorting.
Both NSPredicate and NSSortDescriptor support string-based declarations. This API is old, but it does offer real flexibility. The problem is that this flexibility always comes at the cost of runtime risk: a typo in a string means a runtime error.
This is exactly why SwiftData, SQLiteData, and similar frameworks have moved toward type-safe declaration styles.
But it’s not that simple.
Type safety catches problems earlier, but it often compresses the room for flexible adjustments. For projects that are already in production, already integrated with sync, or whose underlying model can’t easily be touched, there’s often a fragile balance among property names, persistent field names, and query code. When the business vocabulary changes, developers may not actually have the room to adjust the underlying schema.
In these situations, developers often choose to build a mapping layer in Swift rather than modify the underlying model directly:
var title: String {
get {
guard let value = value(forKey: "name") as? String else {
preconditionFailure("Missing required value for `title` (name).")
}
return value
}
set {
setValue(newValue, forKey: "name")
}
}
This does let the presentation layer’s naming align with the current business domain — but new problems emerge:
- The Swift property name diverges from the persistent field name
- Predicates and sort descriptors written with strings become more error-prone
- The hand-written mapping layer grows thicker, and the probability of drift between code and the underlying model increases
So the problem turns out to be more than “string APIs aren’t safe enough.” It’s:
Once a project enters a long-term evolution phase, flexibility, type safety, and model consistency form a sustained tension.
The Real Problem Isn’t Just That the API Is Old — It’s the Growing Dependence on Experience and Discipline
For all the problems above, I’ve written many articles over the years sharing practical approaches for using Core Data in modern projects. But looking back, these experience-based solutions all share a common prerequisite: developers must genuinely understand Core Data’s underlying behavior and be willing to consistently follow an implicit set of conventions.
For seasoned human developers, this may still be achievable. But today, that prerequisite is becoming increasingly unreliable.
On one hand, projects grow larger and team collaboration grows more complex — conventions that only exist as “understood agreements” tend to erode over time. On the other hand, AI-assisted programming is rapidly entering everyday development workflows, and large models naturally produce whatever “common patterns” they saw during pretraining, rather than the subtle but critical conventions your project requires.
This means that experience articles, team discipline, and even Skill-based constraints on AI code generation are no longer sufficient to truly solve the problem.
If a set of practices genuinely fits a modern project, it shouldn’t exist only as “knowledge in a senior developer’s head.” It should be expressed as concretely as possible in:
- Clearer API design
- A more stable source representation layer
- A more verifiable toolchain
- A workflow that both humans and AI can follow consistently
In other words, what Core Data is really missing today may not be more techniques — it’s a way to solidify those techniques into expression and engineering structure.
If You’re Not Replacing Core Data, What Can You Do?
By this point, the problem is fairly clear.
If you’ve already decided to move away from Core Data, this article doesn’t have much to offer you — you’re free to move to SwiftData, SQLiteData, or whatever fits your current needs.
But if you still believe in Core Data’s object graph modeling approach, if you still need its mature migration capabilities and historical compatibility, then the question is no longer “should I leave?” It’s: is there a way to give Core Data a modern Swift expression layer, concurrency model, and workflow — without giving up Core Data itself?
This is the question I’ve been working on, and I’ve put those ideas into an open-source library: CoreDataEvolution.
It’s not a framework to replace Core Data, and it’s not trying to pull developers back to old technology. What it does is closer to filling gaps:
- Using Swift Macros to reshape the model declaration layer, bringing
NSManagedObjectexpression closer to modern Swift semantics - Introducing SwiftData-style actor isolation to give concurrent code clear, verifiable boundaries
- Providing a mapping layer that improves type safety and naming in sort and predicate code, without requiring changes to the underlying model
- Providing a command-line tool to maintain sustainable, automatic alignment between source declarations and the Core Data model
These aren’t a collection of tricks. They’re an attempt to solidify “experience” into engineering structure.
In the next article, I’ll get into the specific design thinking and implementation behind all of this.