Migration et chiffrement des bases Core Data et SwiftData : guide expert complet

La persistance locale est un aspect critique des applications iOS professionnelles. Au-delà du simple stockage, un produit en production doit gérer :

  • l’évolution des modèles (migrations multi-versions),
  • l’intégrité des données en production (compatibilité ascendante),
  • la sécurité (chiffrement, protection physique et logique),
  • la performance lors des mises à jour majeures.

Ce guide fournit une vision exhaustive, orientée production réelle : migrations avancées Core Data, migrations SwiftData via schémas versionnés, stratégie SQLCipher, et bonnes pratiques industrielles. Tout est illustré par des exemples concrets.


1. Migrations Core Data : de la légère au mapping model avancé

Core Data est l’outil le plus mature d'Apple pour gérer le stockage structuré. Il supporte plusieurs stratégies de migration, qui permettent de mettre à jour la structure d’une base existante sans perte de données.

1.1 Migration légère (lightweight migration)

La migration légère est la solution la plus courante. Core Data peut transférer les données vers le nouveau modèle lorsque les modifications sont compatibles :

  • ajout d’un attribut optionnel,
  • suppression d’un attribut optionnel,
  • renommage d’un attribut avec Renaming Identifier,
  • ajout/suppression de relations sans rupture.

Pour activer la migration légère :


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

Exemple de renommage valide :

  • Avant : fullName
  • Après : name avec Renaming Identifier = fullName

1.2 Migration automatique avec inférence du mapping

Core Data peut “deviner” la transformation entre deux versions du modèle lorsque certains changements sont détectables :

  • renommages avec identifiant,
  • ajout d’attributs optionnels,
  • évolution raisonnable du graphe relationnel.

Dans ce mode, Core Data génère lui-même un équivalent minimal de mapping model.

1.3 Migration manuelle : recours au .xcmappingmodel

Lorsque la migration est trop complexe, un mapping explicite est nécessaire. Le mapping model est un fichier .xcmappingmodel généré par Xcode.

On l'utilise lorsque :

  • une entité doit être divisée en plusieurs,
  • deux entités doivent fusionner,
  • une transformation métier est nécessaire (parser un champ, calculer une valeur),
  • on change profondément la structure relationnelle.

1.4 Création d’un mapping model

Dans Xcode :

  1. File > New > File…
  2. Core Data > Mapping Model
  3. Choisir : Source Model (ex. ModelV1), Destination Model (ModelV2)

Chaque “Entity Mapping” décrit :

  • l'entité source,
  • l'entité destination,
  • le type de mapping (Copy, Transform, Custom),
  • les correspondances attribut par attribut,
  • les relations à reconstruire.

1.5 Ajout d’une NSEntityMigrationPolicy

Pour les transformations avancées, on ajoute une classe qui personnalise la migration.


class UserMigrationPolicy: NSEntityMigrationPolicy {

    override func createDestinationInstances(
        forSource sInstance: NSManagedObject,
        in mapping: NSEntityMapping,
        manager: NSMigrationManager
    ) throws {

        let dest = NSEntityDescription.insertNewObject(
            forEntityName: mapping.destinationEntityName!,
            into: manager.destinationContext
        )

        if let full = sInstance.value(forKey: "fullName") as? String {
            let parts = full.split(separator: " ")
            dest.setValue(parts.first.map(String.init), forKey: "firstName")
            dest.setValue(parts.dropFirst().joined(separator: " "), forKey: "lastName")
        }

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

Cette classe doit être référencée dans le Mapping Model, au niveau de l’Entity Mapping.

1.6 Migration multi-étapes (V1 > V2 > V3 > …)

Pour les bases déjà en production depuis plusieurs années, une migration directe V1 > V5 est rarement fiable.

On privilégie la stratégie :

V1 > V2 > V3 > V4 > V5

Chaque étape possède soit :

  • une migration légère,
  • un mapping model précis.

1.7 Migration manuelle via NSMigrationManager

Cas extrêmes : migration bloquante, corruption, changements atypiques. On pilote la migration manuellement.


let migrationManager = NSMigrationManager(
    sourceModel: sourceModel,
    destinationModel: destModel
)

try migrationManager.migrateStore(
    from: sourceURL,
    sourceType: NSSQLiteStoreType,
    options: nil,
    with: mappingModel,
    toDestinationURL: destURL,
    destinationType: NSSQLiteStoreType,
    destinationOptions: nil
)

2. Migrations SwiftData : VersionedSchema, plans de migration et transformations

SwiftData introduit un nouveau paradigme de migration, beaucoup plus structuré que la migration automatique de Core Data.

Le principe clé :

Chaque version de votre modèle SwiftData est exprimée via un “VersionedSchema”.

2.1 Déclarer les schémas versionnés

Exemple : Version 1 du modèle Note.


enum AppSchemaV1: VersionedSchema {
    static var versionIdentifier = Schema.Version(1, 0, 0)

    static var models: [any PersistentModel.Type] {
        [Note.self]
    }

    @Model
    final class Note {
        var title: String
        var content: String
    }
}

Version 2 du même modèle avec ajout d’un champ.


enum AppSchemaV2: VersionedSchema {
    static var versionIdentifier = Schema.Version(2, 0, 0)

    static var models: [any PersistentModel.Type] {
        [Note.self]
    }

    @Model
    final class Note {
        var title: String
        var content: String
        var createdAt: Date
    }
}

2.2 Plan de migration (SchemaMigrationPlan)

Le plan décrit comment passer d’une version à la suivante.


enum AppMigrationPlan: SchemaMigrationPlan {

    static var schemas: [VersionedSchema.Type] {
        [
            AppSchemaV1.self,
            AppSchemaV2.self
        ]
    }

    static var stages: [MigrationStage] {
        [
            .lightweight(from: AppSchemaV1.self, to: AppSchemaV2.self)
        ]
    }
}

2.3 Migration personnalisée

Exemple d’une transformation custom pour convertir un champ.


static var stages: [MigrationStage] {
    [
        .custom(
            from: AppSchemaV1.self,
            to: AppSchemaV2.self,
            willMigrate: { context in
                // Préparation éventuelle
            },
            didMigrate: { context in
                // Exemple : initialiser createdAt manuellement
                let descriptor = FetchDescriptor<AppSchemaV2.Note>()
                let notes = try context.fetch(descriptor)
                for note in notes {
                    note.createdAt = .now
                }
                try context.save()
            }
        )
    ]
}

2.4 Initialisation du container avec migration


let container = try ModelContainer(
    for: AppSchemaV2.Note.self,
    migrationPlan: AppMigrationPlan.self,
    configurations: [
        ModelConfiguration(schema: AppSchemaV2.self)
    ]
)

Ce mécanisme permet des migrations réelles multi-étapes comparable à Core Data mais plus modernes.


3. Chiffrement : protéger les bases Core Data et SwiftData

Le chiffrement dans iOS repose sur trois couches :

  • le chiffrement matériel du disque,
  • les protections de fichiers (NSFileProtection),
  • un chiffrement applicatif ou SQLCipher.

3.1 Protection Apple : NSFileProtection

On peut définir une politique de protection pour le fichier SQLite :


let description = NSPersistentStoreDescription(url: storeURL)
description.setOption(
    FileProtectionType.complete as NSObject,
    forKey: NSPersistentStoreFileProtectionKey
)

Options principales :

  • .complete (le plus sécurisé),
  • .completeUnlessOpen,
  • .completeUntilFirstUserAuthentication.

3.2 SQLCipher + Core Data : chiffrement total de la base

Pour chiffrer réellement le contenu SQLite, on utilise SQLCipher :


let description = NSPersistentStoreDescription(url: storeURL)
description.type = NSSQLiteStoreType
description.setOption("PRAGMA key = 'monmotdepasse';" as NSString,
                      forKey: "sqlite pragmas")

SQLCipher fournit :

  • chiffrement AES-256,
  • pages SQLite chiffrées,
  • résistance même à l’extraction physique du fichier.

3.3 SwiftData + chiffrement : état actuel

SwiftData n’offre pas encore d’API directe pour remplacer SQLite par SQLCipher : il utilise une infrastructure Core Data interne non exposée.

Approches réalistes aujourd'hui :

  • NSFileProtection pour éviter les accès non autorisés hors device déverrouillé,
  • chiffrement applicatif (ex. AES dans les champs sensibles),
  • migration depuis SQLCipher via une lecture manuelle puis insertion dans SwiftData.

3.4 Exemple : migration SQLCipher > SwiftData

On ouvre l’ancienne base, on lit les lignes, on recrée les objets SwiftData.


func migrateFromSQLCipher(to context: ModelContext) throws {

    let oldDB = try openEncryptedDB(path: "...", key: "secret")

    for row in oldDB.fetchRows("SELECT title, content FROM notes") {
        let note = Note(title: row["title"], content: row["content"])
        context.insert(note)
    }

    try context.save()
}

3.5 Chiffrement applicatif (AES + Keychain)

Pour un contrôle total, on chiffre les données avant stockage :


let key = CryptoManager.shared.loadKeyFromKeychain()

let encryptedData = try AES.GCM.seal(
    content.data(using: .utf8)!,
    using: key
).combined

note.encryptedContent = encryptedData

Cette méthode garantit qu'aucune donnée sensible n’apparaît “en clair” dans la base SwiftData ou Core Data.


4. Stratégies professionnelles pour les migrations et la sécurité

4.1 Migrations Core Data : bonnes pratiques

  • Une version > un modèle : éviter les suppressions d’anciennes versions.
  • Préférer plusieurs petites migrations plutôt qu’une migration géante.
  • Systématiser les “renaming identifiers”.
  • Éviter les changements destructifs sans mapping model explicite.

4.2 Migrations SwiftData : bonnes pratiques

  • Bien séparer chaque version avec VersionedSchema.
  • Utiliser SchemaMigrationPlan pour conserver la lisibilité.
  • Tester chaque étape avec une archive de données réelle.
  • Préparer des migrations custom pour des transformations métier.

4.3 Sécurité & chiffrement : recommandations

  • Utiliser NSFileProtection.complete par défaut.
  • SQLCipher recommandé pour les applications réglementées (finance, santé).
  • Chiffrement applicatif pour champs sensibles (identifiants, tokens, documents privés).
  • Rotation de clés via Keychain Access.

Conclusion

Core Data et SwiftData offrent des mécanismes puissants de migration et de sécurité, mais leurs logiques diffèrent profondément :

  • Core Data : système mature, migrations flexibles, compatible SQLCipher.
  • SwiftData : versioning structuré, migrations déclaratives, API moderne.

Le chiffrement repose sur un ensemble complémentaire d’outils :

  • protection système (NSFileProtection),
  • chiffrement structurel (SQLCipher),
  • chiffrement applicatif (AES + Keychain).

Avec ces techniques combinées, il est possible de créer des applications iOS robustes, sûres, capables d’évoluer sur plusieurs années avec une base de données maîtrisée, migrable et protégée.