← All writing
HackerNoonAugust 2026

SwiftData, Core Data, or GRDB: Choose by the Queries You Actually Run

Framework comparisons for iOS persistence often line three sample models up next to each other and let the tidiest syntax win. What should decide instead is the list of operations that data actually needs to support, plus whether the product will eventually ask for sync across devices.

The recommendation follows from those operations. Plain queries in a SwiftUI app point at SwiftData — with the caveat, worked through explicitly, that the obvious model will not sync with CloudKit. An existing stack points at Core Data. When the queries themselves are the hard part — complex predicates, full-text search, large batch updates — the answer is SQLite through GRDB, because that is the control the higher-level frameworks do not hand back.

Around the choice sit the decisions that outlive it: don't pass persistence models through the whole app, return values rather than live database objects across concurrency boundaries, and plan migrations before the first release rather than after it.

This is the second half of a pair. “iOS Data Storage: Classify Your Data Before You Choose a Database” sorts everything a travel-planning app stores into six categories and settles five of them without a database at all; this one deals with the sixth.

What's inside

  • It opens by listing eight concrete operations a sample trip-planning app has to support — fetching trips, searching places, grouping expenses by currency, batch-updating synced rows, deleting a trip and its children — and measures every framework against that same list rather than against syntax.
  • It shows how a SwiftData model that looks perfectly ordinary, with a unique identifier and a required relationship, can fail to open the moment CloudKit sync is turned on, then lays out the handful of schema rules — optional relationships, no uniqueness checks, no forbidding delete rules — a syncable model has to follow instead.
  • For teams already on Core Data, it names where that framework still has an edge over SwiftData: updating many rows without materializing objects for each one, and a fetched-results controller built to animate long, sectioned lists — alongside its strict rule against passing managed objects between queues.
  • It argues for reaching past both frameworks into SQLite through GRDB once full-text search, grouped totals, or large batch writes are the actual bottleneck, and flags a behavioral change between GRDB releases in how a cancelled task affects a read already in flight.
  • A section on application structure makes the case for hiding whichever engine is chosen behind a repository interface instead of letting its model types spread into view models and background jobs, and points out a new SwiftData API that brings live-query updates to that boundary without requiring SwiftUI.
  • It closes with rules for isolating background writers safely and a migration checklist, including why bolting sync onto a model already in users' hands forces a migration across every installed copy of the app.
The takeaway

The underlying position is that none of the three frameworks wins outright — the right one falls out of which of the sample operations actually dominate, and whether the data needs to travel between a person's devices at all. That call is cheapest to make before any model code is written, since a schema built for local-only storage is expensive to convert once synchronization is added later.

Read the full framework comparison, with schemas and code, on HackerNoon ↗
Part of a pairiOS Data Storage: Classify Your Data Before You Choose a Database