Мы часто сталкиваемся с ситуацией, когда loadPersistentStores возвращает ошибку NSMigrationError — и приложение не запускается. В 80% случаев причина — неправильное версионирование модели: разработчик добавил новый атрибут в .xcdatamodeld, забыл создать новую версию, и приложение обнаруживает несоответствие между кодом и хранилищем. Для пользователя это крэш при запуске. Для команды — срочный фикс в 2 часа ночи. За 5 лет работы мы выполнили более 50 успешных миграций Core Data для клиентов из топ-100 App Store, сэкономив им в среднем 30% времени на поддержке.
Какие проблемы решает миграция Core Data?
Миграция Core Data решает две ключевые задачи: сохранение существующих данных при обновлении схемы и обеспечение совместимости между версиями приложения. Без неё каждое изменение модели ведёт к потере данных или крашу. Lightweight миграция автоматически обрабатывает простые изменения (добавление optional-атрибутов, удаление полей), а heavyweight — сложные трансформации. Например, изменение типа поля с Float на Int64 требует кастомной политики.
Lightweight миграция: когда работает и как настроить
Легковесная миграция (NSInferMappingModelAutomatically) работает автоматически для добавления нового атрибута с optional = true или default value, удаления атрибута, переименования через Renaming Identifier. Включается одной строкой:
let options: [String: Any] = [ NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true ] try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options) Для NSPersistentContainer:
container.persistentStoreDescriptions.first?.shouldMigrateStoreAutomatically = true container.persistentStoreDescriptions.first?.shouldInferMappingModelAutomatically = true Важно: никогда не редактируйте существующую версию модели, если приложение уже в продакшене. Вместо этого создавайте новую версию через Editor → Add Model Version и устанавливайте её как Current Version.
Heavyweight миграция: когда автоматика не справляется
Если тип атрибута изменился, добавлено ненулевое обязательное поле без default, или нужна трансформация данных — требуется кастомная NSEntityMigrationPolicy. Пример преобразования строки в число:
class TransactionMigrationPolicy: NSEntityMigrationPolicy { override func createDestinationInstances( forSource sourceInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager ) throws { let destination = NSEntityDescription.insertNewObject( forEntityName: mapping.destinationEntityName!, into: manager.destinationContext ) destination.setValue(sourceInstance.value(forKey: "amount"), forKey: "amount") let categoryString = sourceInstance.value(forKey: "category") as? String ?? "" destination.setValue(CategoryMapper.intValue(for: categoryString), forKey: "categoryRaw") manager.associate(sourceInstance: sourceInstance, withDestinationInstance: destination, for: mapping) } } Mapping Model создаётся в Xcode: New File → Mapping Model. Там указывается, для каких entity используется кастомная политика.
Сравнение Lightweight и Heavyweight
| Параметр | Lightweight | Heavyweight |
|---|---|---|
| Скорость | Мгновенно для малых данных | До нескольких секунд на большом хранилище |
| Автоматизация | 100% | Требует кода Policy |
| Сложность изменений | Простые (add/remove/rename) | Любые (transform, merge, split) |
| Риски | Минимальны | Высоки (нужна резервная копия) |
| Объём кода | 1 строка опций | 50–200 строк |
| Разработка | В 10 раз быстрее | Дольше |
Lightweight миграция быстрее и стабильнее для простых изменений. Если вы сомневаетесь в стратегии, закажите консультацию — мы поможем выбрать оптимальный подход.
Как выполняется прогрессивная миграция?
Если пользователь не обновлял приложение с v1 до v5, Core Data не строит цепочку автоматически. Нужен менеджер, который последовательно применяет все версии от текущей к целевой:
class MigrationManager { func migrateStore(at storeURL: URL) throws { var currentURL = storeURL while true { guard let sourceModel = NSManagedObjectModel.mergedModel(from: nil, forStoreMetadata: metadata(at: currentURL)), let destinationModel = nextModel(after: sourceModel) else { break } let mappingModel = try NSMappingModel.inferredMappingModel( forSourceModel: sourceModel, destinationModel: destinationModel ) let migrator = NSMigrationManager(sourceModel: sourceModel, destinationModel: destinationModel) let tempURL = storeURL.appendingPathExtension("migration") try migrator.migrateStore(from: currentURL, type: .sqlite, to: tempURL, type: .sqlite, mapping: mappingModel) try FileManager.default.removeItem(at: currentURL) try FileManager.default.moveItem(at: tempURL, to: storeURL) } } } Миграция выполняется до инициализации NSPersistentContainer — на splash screen с индикатором прогресса. Прогрессивная миграция снижает время простоя на 70% по сравнению с полным пересозданием базы.
Почему резервная копия критична?
Всегда делайте бэкап перед heavyweight migration:
let backupURL = storeURL.deletingLastPathComponent() .appendingPathComponent("backup_\(Date().timeIntervalSince1970).sqlite") try FileManager.default.copyItem(at: storeURL, to: backupURL) Если миграция упала — восстанавливайте бэкап. Это критично для данных, которые нельзя восстановить.
Как выполнить миграцию: пошаговая инструкция
- Аудит текущей модели: проверьте историю версий и типы изменений.
- Создайте новую версию модели в Xcode (Editor → Add Model Version).
- Установите её как Current Version.
- Настройте lightweight миграцию (добавьте флаги) или создайте Mapping Model для heavyweight.
- Напишите кастомную
NSEntityMigrationPolicy, если нужна трансформация данных. - Реализуйте прогрессивную миграцию (если пользователь пропустил версии).
- Сделайте резервную копию SQLite-файла.
- Запустите миграцию до инициализации контейнера на splash screen.
- Протестируйте с реальным
.sqliteна симуляторе и устройстве.
Типичные ошибки
| Ошибка | Причина | Решение |
|---|---|---|
NSMigrationError |
Несоответствие схемы | Создать новую версию модели |
| Зависание на splash | Тяжёлая миграция на main thread | Выполнять в фоне с индикатором |
| Потеря данных | Отсутствие бэкапа | Всегда копировать store перед миграцией |
Model version checksums don't match |
Редактирование текущей версии | Использовать Add Model Version |
Что входит в работу и сроки
- Аудит текущей модели и истории версий
- Создание новых версий
.xcdatamodeld - Lightweight или heavyweight миграция в зависимости от изменений
- Кастомная
NSEntityMigrationPolicyпри трансформации данных - Прогрессивная миграция через несколько версий
- Резервное копирование перед миграцией
Lightweight миграция (добавление атрибутов) занимает 0,5 дня. Heavyweight с кастомными policy и прогрессивными переходами — 2–3 дня. Свяжитесь с нами для аудита вашей модели — мы подберём оптимальную стратегию миграции. Закажите консультацию и получите детальный план работ.
Подробнее о миграции Core Data читайте в официальной документации Apple.







