SwiftData or Core Data in 2026

SwiftData (iOS 17+, 2023) and Core Data (iOS 3+, 2009) are both Apple’s object graph and persistence frameworks. SwiftData provides a modern Swift-native API; Core Data remains available for complex needs and backward compatibility.

Model Declaration

SwiftData uses @Model macros on plain Swift classes:

import SwiftData

@Model
final class Recipe {
  var name: String
  var servings: Int
  var ingredients: [Ingredient]

  init(name: String, servings: Int, ingredients: [Ingredient]) {
    self.name = name
    self.servings = servings
    self.ingredients = ingredients
  }
}

Core Data commonly uses an .xcdatamodeld file and generated or handwritten NSManagedObject subclasses:

import CoreData

@objc(Recipe)
final class Recipe: NSManagedObject {
  @NSManaged var name: String
  @NSManaged var servings: Int16
  @NSManaged var ingredients: NSSet?
}

SwiftData eliminates the separate model editor and uses Swift-native declarations. Core Data can also use a programmatically constructed model or generic NSManagedObject instances, but the model editor and subclasses are the usual approach. Its generated subclasses expose Objective-C-compatible types such as Int16, Double, and NSSet.

Fetching Data

SwiftData provides SwiftUI-native property wrappers and a FetchDescriptor API:

@Query(filter: #Predicate<Recipe> { $0.servings > 4 })
var largeRecipes: [Recipe]

// Programmatic
let descriptor = FetchDescriptor<Recipe>(
  predicate: #Predicate { $0.name.contains("Soup") },
  sortBy: [SortDescriptor(\.name)]
)
let results = try context.fetch(descriptor)

Core Data uses NSFetchRequest with string-based key paths and NSPredicate:

let request = NSFetchRequest<Recipe>(entityName: "Recipe")
request.predicate = NSPredicate(format: "servings > %d", 4)
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
let results = try context.fetch(request)

In SwiftUI, Core Data needs an @FetchRequest wrapper, while SwiftData’s @Query supports typed #Predicate syntax at the call site. Both work, but SwiftData’s #Predicate catches type errors at compile time.

Concurrency

SwiftData introduces @ModelActor for safe background work:

@ModelActor
actor RecipeHandler {
  func importRecipes() async throws {
    // self.modelContext is already on the correct executor
  }
}

Core Data requires manual performBackgroundTask or child contexts:

persistentContainer.performBackgroundTask { context in
  // context is confined to this queue
}

SwiftData’s actor-based model is simpler and harder to misuse. Core Data’s approach is battle-tested but verbose.

Schema Migration

SwiftData handles simple migrations, such as adding properties, automatically. For renamed properties, preserve the previous name with @Attribute(originalName:). For complex transforms, use a SchemaMigrationPlan with MigrationStage definitions:

enum RecipeMigrationPlan: SchemaMigrationPlan {
  static var schemas: [any VersionedSchema.Type] {
    [SchemaV1.self, SchemaV2.self]
  }

  static var stages: [MigrationStage] {
    [migrateV1toV2]
  }

  static let migrateV1toV2 = MigrationStage.custom(
    fromVersion: SchemaV1.self,
    toVersion: SchemaV2.self,
    willMigrate: { context in
      // clean up data before the schema changes
    },
    didMigrate: nil
  )
}

Core Data supports lightweight and heavyweight migrations via mapping models and NSEntityMigrationPolicy. It handles any migration you can imagine, but the setup is significantly more involved.

CloudKit

Both frameworks support CloudKit sync. SwiftData automatically discovers the primary CloudKit container from the app’s entitlements by default. You can also specify a private container explicitly:

let config = ModelConfiguration(
  cloudKitDatabase: .private("iCloud.com.example.Recipes")
)

SwiftData sync also requires the iCloud and Background Modes capabilities and a CloudKit-compatible schema. Core Data requires a NSPersistentCloudKitContainer plus additional configuration for shared and public databases. For automatic sync, SwiftData is dramatically simpler. For advanced scenarios, Core Data still offers more control.

Recent SwiftData Updates

iOS 18 and Xcode 16 brought several improvements to SwiftData:

Feature Description
#Index macro Declare database indexes on model properties for faster queries
#Unique macro Enforce uniqueness constraints across model instances
DataStore protocol Replace the default store with a custom persistence backend
#Expression macro Compose richer expressions for use in predicates

These additions close the gap with Core Data for many common scenarios.

iOS 26 adds model inheritance and improves history queries with fetch limits and sorting. These changes make SwiftData more capable for new apps, but Core Data still has the longer production track record and broader compatibility.

Which One to Choose

Situation Recommendation
New project, iOS 18+ SwiftData. You get modern Swift syntax, less boilerplate, and the 2.0 additions cover most needs.
New project, iOS 17 SwiftData still works, but be aware of its earlier limitations (no indexes, no custom stores).
Existing Core Data project Keep Core Data unless SwiftData offers a clear benefit. Apple documents incremental adoption and coexistence, so migration does not need to be all-or-nothing.
Complex migrations Core Data. Its heavyweight migration system is unmatched.
Shared/public CloudKit Core Data if you need more than automatic sync; SwiftData otherwise.
Pre- iOS 17 support Core Data is your only option.