Миграции баз данных в Swift: инструменты и практики для iOS/macOS

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Миграции баз данных в Swift (iOS/macOS)

Разработка приложений для iOS и macOS часто требует работы с локальными базами данных. По мере развития приложения структура данных неизбежно меняется: добавляются новые поля, изменяются типы или удаляются устаревшие сущности. Без правильного механизма миграций эти изменения могут привести к потере пользовательских данных или падению приложения. В этой статье мы рассмотрим основные инструменты и подходы для выполнения миграций баз данных в Swift, а также приведем практические примеры для популярных решений.

Почему миграции важны?

Миграции баз данных обеспечивают:

  • Сохранность данных — пользовательские записи не теряются при обновлении схемы.
  • Обратную совместимость — приложение продолжает работать на старых версиях данных.
  • Гибкость разработки — возможность итеративно менять модель данных без ручного вмешательства.

Основные инструменты для миграций в Swift

1. Core Data + NSPersistentContainer

Встроенный фреймворк Apple предоставляет автоматические и легковесные миграции. Для сложных изменений используется ручное версионирование модели.

// Настройка контейнера с поддержкой миграций
let container = NSPersistentContainer(name: "MyAppModel")
container.persistentStoreDescriptions.first?.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions.first?.shouldInferMappingModelAutomatically = true

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

Для ручной миграции создаются версии модели (.xcdatamodeld) и NSMappingModel:

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 — современный SQLite-драйвер

GRDB.swift предлагает гибкий механизм миграций на основе регистрации шагов. Миграции выполняются последовательно и атомарно.

import GRDB

var migrator = DatabaseMigrator()

// Регистрация первой миграции
migrator.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()
    }
}

// Регистрация второй миграции (добавление поля)
migrator.registerMigration("v2_add_age") { db in
    try db.alter(table: "users") { t in
        t.add(column: "age", .integer).defaults(to: 0)
    }
}

// Применение миграций при старте приложения
let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")
try migrator.migrate(dbQueue)

Преимущества GRDB.swift: типизированные запросы, поддержка транзакций, простая отмена миграций.

3. Realm Swift (сейчас Atlas Device SDK)

Realm автоматически обрабатывает большинство миграций, но для сложных изменений требуется явное обновление схемы.

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"] ?? "")"
            }
        }
        if oldSchemaVersion < 2 {
            migration.enumerateObjects(ofType: "User") { _, newObject in
                newObject?["isActive"] = true
            }
        }
    }
)

let realm = try! Realm(configuration: config)

Важно: при изменении названия поля или типа данных используйте migration.renameProperty(onType:from:to:).

4. Ручной подход на SQLite (FMDB или SQLite.swift)

Для максимального контроля можно использовать низкоуровневые библиотеки. Пример с SQLite.swift:

import SQLite

let db = try Connection("path/to/db.sqlite3")
let currentVersion = try db.scalar("PRAGMA user_version") as? Int ?? 0

if currentVersion < 1 {
    try db.run("""
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            email TEXT UNIQUE
        )
    """)
    try db.run("PRAGMA user_version = 1")
}

if currentVersion < 2 {
    try db                    

Рекомендаций