Database Migrations in Swift: Tools and Practices for iOS/macOS

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Database Migrations in Swift (iOS/macOS)



Developing applications for iOS and macOS often requires working with local databases. As the application evolves, the data structure inevitably changes: new fields are added, types are modified, or outdated entities are removed. Without a proper migration mechanism, these changes can lead to loss of user data or application crashes. In this article, we will review the main tools and approaches for performing database migrations in Swift, and provide practical examples for popular solutions.



Why are migrations important?

Database migrations ensure:

  • Data preservation — user records are not lost when the schema is updated.
  • Backward compatibility — the application continues to work with older versions of the data.
  • Development flexibility — the ability to iteratively change the data model without manual intervention.


Main tools for migrations in Swift



1. Core Data + NSPersistentContainer

Apple's built-in framework provides automatic and lightweight migrations. For complex changes, manual model versioning is used.

// Configuring the container with migration supportlet container = NSPersistentContainer(name: "MyAppModel")container.persistentStoreDescriptions.first?.shouldMigrateStoreAutomatically = truecontainer.persistentStoreDescriptions.first?.shouldInferMappingModelAutomatically = true

container.loadPersistentStores { description, error in if let error = error { fatalError("Failed to load store: \\(error)") }}

For manual migration, model versions (.xcdatamodeld) and NSMappingModel are created:

let sourceModel = NSManagedObjectModel(contentsOf: sourceURL)!let destinationModel = NSManagedObjectModel(contentsOf: destinationURL)!let mappingModel = NSMappingModel(from: [sourceModel, destinationModel], forSourceModel: sourceModel, destinationModel: destinationModel)

let manager = NSMigrationManager(sourceModel: sourceModel, destinationModel: destinationModel)try? manager.migrateStore(from: sourceURL, sourceType: NSSQLiteStoreType, options: nil, with: mappingModel, toDestinationURL: destinationURL, destinationType: NSSQLiteStoreType, destinationOptions: nil)


2. GRDB.swift — modern SQLite driver

GRDB.swift offers a flexible migration mechanism based on registering steps. Migrations are executed sequentially and atomically.

import GRDB

var migrator = DatabaseMigrator()

// Registering the first migrationmigrator.registerMigration("v1_create_users") { db in try db.create(table: "users") { t in t.autoIncrementedPrimaryKey("id") t.column("name", .text).notNull() t.column("email", .text).unique() }}

// Registering the second migration (adding a field)migrator.registerMigration("v2_add_age") { db in try db.alter(table: "users") { t in t.add(column: "age", .integer).defaults(to: 0) }}

// Applying migrations on app startuplet dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")try migrator.migrate(dbQueue)

Advantages of GRDB.swift: typed queries, transaction support, easy migration rollback.



3. Realm Swift (now Atlas Device SDK)

Realm automatically handles most migrations, but for complex changes, explicit schema updates are required.

import RealmSwift

let config = Realm.Configuration( schemaVersion: 2, migrationBlock: { migration, oldSchemaVersion in if oldSchemaVersion < 1 { migration.enumerateObjects(ofType: "User") { oldObject, newObject in newObject?["fullName"] = "\\(oldObject!["firstName"] ?? "") \\(oldObject!["lastName"] ?? "")" }

Recommendations