Core Data schema migration in iOS app

NOVASOLUTIONS.TECHNOLOGY is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1 servicesAll 1735 services
Core Data schema migration in iOS app
Medium
~2-3 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

Implementing Database Schema Migration (Core Data Migration) in iOS Applications

loadPersistentStores returned an NSMigrationError — and the app won't launch. This happens when a developer added a new attribute to .xcdatamodeld, forgot to create a new model version, and the app discovered a mismatch between code and storage. For users this is a crash on launch. For the team — urgent 2 AM fix.

Data Model Versioning

Core Data stores all model versions in xcdatamodeld package (a folder with *.xcdatamodel files inside). Active version is indicated in .xccurrentversion. When changing schema:

  1. In Xcode: Editor → Add Model Version
  2. Set new version as Current Version
  3. Describe the migration

Never edit an existing model version if the app is already in production — this guarantees a crash for all users.

Lightweight Migration — When It Works

Lightweight migration (NSInferMappingModelAutomatically) works automatically for:

  • Adding new attribute with optional = true or with defaultValue
  • Deleting attribute
  • Renaming entity or attribute with Renaming Identifier

Enabled with one line:

let options: [String: Any] = [
    NSMigratePersistentStoresAutomaticallyOption: true,
    NSInferMappingModelAutomaticallyOption: true
]
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options)

If Renaming Identifier in .xcdatamodeld is set correctly — Core Data builds mapping between versions itself. For NSPersistentContainer:

container.persistentStoreDescriptions.first?.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions.first?.shouldInferMappingModelAutomatically = true

Heavyweight Migration — When Automation Doesn't Work

If attribute type changed, required non-zero field without default added, or data transformation is needed during migration — need custom 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
        )
        // Copy attributes
        destination.setValue(sourceInstance.value(forKey: "amount"), forKey: "amount")
        // Transform: old String → new enum Int
        let categoryString = sourceInstance.value(forKey: "category") as? String ?? ""
        destination.setValue(CategoryMapper.intValue(for: categoryString), forKey: "categoryRaw")

        manager.associate(sourceInstance: sourceInstance, withDestinationInstance: destination, for: mapping)
    }
}

NSEntityMigrationPolicy is specified in MappingModel.xcmappingmodel — file created via Xcode: New File → Mapping Model. It describes mapping of entity from old version → entity in new version and which Policy class to use.

Progressive Migration Through Multiple Versions

If user hasn't updated app from v1 to v5 — Core Data doesn't automatically build migration chains. Need a manager:

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)
        }
    }
}

Migration runs before NSPersistentContainer initialization — on splash screen with progress indicator.

Backup Before Migration

Always backup before heavyweight migration:

let backupURL = storeURL.deletingLastPathComponent()
    .appendingPathComponent("backup_\(Date().timeIntervalSince1970).sqlite")
try FileManager.default.copyItem(at: storeURL, to: backupURL)

If migration fails — restore backup. Critical for non-recoverable data.

Typical Mistakes

  • Editing current model version instead of creating new one — Model version checksums don't match
  • Heavy migration on main thread — UI hangs for several seconds on large database
  • Not testing migration with real .sqlite file — errors discovered only on user device

Work Scope

  • Audit current model and version history
  • Create new .xcdatamodeld versions
  • Lightweight or heavyweight migration depending on changes
  • Custom NSEntityMigrationPolicy for data transformation
  • Progressive migration through multiple versions
  • Backup before migration

Timeline

Lightweight migration (adding attributes): 0.5 day. Heavyweight with custom policies and progressive transitions through several versions: 2–3 days.