Миграции баз данных в 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 Также в библиотеке
HTTP-клиент для Go (Golang): топ библиотек для работы с HTTP
Генератор QR-кодов на Go (Golang): библиотека go-qrcode
Elasticsearch клиент для C# (.NET): NEST и Elastic.Clients.Elasticsearch