Crash by SwiftData MigarionPlan

I am a develop beginner. Recently, my App used SwiftData's MigraitonPlan, which caused it to crash when I opened it for the first time after updating in TestFlight or the store. After clicking it a second time, I could enter the App normally, and I could confirm that all the models were the latest versions.However, when I tested it in Xcode, everything was normal without any errors.

Here is my MigrationPlan code:

import Foundation
import SwiftData

enum MeMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [MeSchemaV1.self, MeSchemaV2.self, MeSchemaV3.self, MeSchemaV4.self]
    }

    static var stages: [MigrationStage] {
        [migrateV1toV2, migrateV2toV3, migrateV3toV4]
    }

//migrateV1toV2, because the type of a data field in MeSchemaV1.TodayRingData.self was modified, the historical data was deleted during the migration, and the migration work was successfully completed.

    static let migrateV1toV2 = MigrationStage.custom(
        fromVersion: MeSchemaV1.self,
        toVersion: MeSchemaV2.self,
        willMigrate: { context in
                      try context.delete(model: MeSchemaV1.TodayRingData.self)
                     },
        didMigrate: nil
    )

//migrateV2toV3, because a new Model was added, it would crash at startup when TF and the official version were updated, so I tried to delete the historical data during migration, but the problem still exists.

    static let migrateV2toV3 = MigrationStage.custom(
        fromVersion: MeSchemaV2.self,
        toVersion: MeSchemaV3.self,
        willMigrate: { context in
                      try context.delete(model: MeSchemaV2.TodayRingData.self)
                      try context.delete(model: MeSchemaV2.HealthDataStatistics.self)
                      try context.delete(model: MeSchemaV2.SportsDataStatistics.self)
                      try context.delete(model: MeSchemaV2.UserSettingTypeFor.self)
                      try context.delete(model: MeSchemaV2.TodayRingData.self)
                      try context.delete(model: MeSchemaV2.TodayHealthData.self)
                      try context.delete(model: MeSchemaV2.SleepDataSource.self)
                      try context.delete(model: MeSchemaV2.WorkoutTargetData.self)
                      try context.delete(model: MeSchemaV2.WorkoutStatisticsForTarget.self)
                      try context.delete(model: MeSchemaV2.HealthDataList.self)
                     },
        didMigrate: nil
    )

//migrateV3toV4, adds some fields in MeSchemaV3.WorkoutList.self, and adds several new Models. When TF and the official version are updated, it will crash at startup. Continue to try to delete historical data during migration, but the problem still exists.

    static let migrateV3toV4 = MigrationStage.custom(
        fromVersion: MeSchemaV3.self,
        toVersion: MeSchemaV4.self,
        willMigrate: { context in
                      do {
                          try context.delete(model: MeSchemaV3.WorkoutList.self)
                          try context.delete(model: MeSchemaV3.HealthDataStatistics.self)
                          try context.delete(model: MeSchemaV3.SportsDataStatistics.self)
                          try context.delete(model: MeSchemaV3.UserSettingTypeFor.self)
                          try context.delete(model: MeSchemaV3.TodayRingData.self)
                          try context.delete(model: MeSchemaV3.TodayHealthData.self)
                          try context.delete(model: MeSchemaV3.SleepDataSource.self)
                          try context.delete(model: MeSchemaV3.WorkoutTargetData.self)
                          try context.delete(model: MeSchemaV3.WorkoutStatisticsForTarget.self)
                          try context.delete(model: MeSchemaV3.HealthDataList.self)
                          try context.delete(model: MeSchemaV3.SleepStagesData.self)

                          try context.save() 
                      } catch {
                          print("Migration from V3 to V4 failed with error: \(error)")
                          throw error
                      }
                     },
        didMigrate: nil
    )
}

The initialization code of this ModelContainer:

import SwiftUI
import SwiftData

@main
struct MeApp: App {
    @StateObject var viewModel  = ViewModel()
    let container: ModelContainer
    init() {
        do {
            let schema = Schema([
                WorkoutList.self,
                HealthDataStatistics.self,
                SportsDataStatistics.self,
                UserSettingTypeFor.self,
                TodayRingData.self,
                TodayHealthData.self,
                SleepDataSource.self,
                WorkoutTargetData.self,
                WorkoutStatisticsForTarget.self,
                HealthDataList.self,
                SleepStagesData.self,
                WorkoutDetailData.self,
                HeartZoneData.self,
                WorkoutSegmentData.self,
                WorkoutMaxMinRangeData.self,
                WorkoutHeartZoneData.self
            ])
            container = try ModelContainer(
                for: schema,
                migrationPlan: MeMigrationPlan.self
            )
            print("ModelContainer initialized successfully")
        } catch {
            print("Error initializing model container: \(error)")
            fatalError("Failed to initialize model container.")
        }
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView( switchSportsType: sst)
                .environmentObject(viewModel)
                .modelContainer(container)
        }
    }
}

Logs obtained through TestFlight's Crashlog file

Thread 0 name:
Thread 0 Crashed:
0   libswiftCore.dylib            	0x00000001a45f58c0 _assertionFailure(_:_:file:line:flags:) + 264 (AssertCommon.swift:144)
1   Me                            	0x00000001053746e0 specialized MeApp.init() + 1780 (MeApp.swift:59)
2   Me                            	0x00000001053736d8 MeApp.init() + 8 (<compiler-generated>:0)
3   Me                            	0x00000001053736d8 protocol witness for App.init() in conformance MeApp + 28
4   SwiftUI                       	0x00000001a9e904c0 static App.main() + 116 (App.swift:114)
5   Me                            	0x000000010537374c static MeApp.$main() + 24 (MeApp.swift:0)
6   Me                            	0x000000010537374c main + 36
7   dyld                          	0x00000001c9399e4c start + 2240 (dyldMain.cpp:1298)

Use "-com.apple.CoreData.SQLDebug 1" to output the log in the Xcode console:

CoreData: annotation: Completed persistent history metadata tables update
CoreData: annotation: Updating metadata
CoreData: annotation: Finished updating metadata
CoreData: annotation: Starting inferred mapping validation
CoreData: annotation: Executing inferred mapping validation: IEM_Transform_WorkoutList
CoreData: annotation: Finished inferred mapping validation
CoreData: annotation: Committing formal transaction
CoreData: annotation: Finished committing formal transaction
CoreData: annotation: Checkpointing WAL journal
CoreData: annotation: Finished checkpointing WAL journal
CoreData: annotation: Successfully completed lightweight migration on connection
CoreData: annotation:     Migration step 0.0 'Total migration time (on connection)' took 0.05 seconds
CoreData: annotation:     Migration step 2.0 'Total formal transaction time' took 0.04 seconds
CoreData: annotation:     Migration step 2.5 'Execution of entity schema and data migration statements' took 0.02 seconds
CoreData: annotation: (migration) in-place migration completed successfully in 0.06 seconds
CoreData: annotation: (migration)	 Automatic schema migration succeeded for store 

The stack trace in your crash report shows that your app crashed at the following fatalError when creating the model container:

do { ... container = try ModelContainer( ... } catch { print("Error initializing model container: (error)") fatalError("Failed to initialize model container.") }

To debug the issue, you might look into the description of error first, which typically contains a hint about why the error happened.

To do so, try to reproduce the issue with your Xcode build first, and find the output of the print in your Xcode console. The reason your Xcode build can't reproduce the issue might be because the store to be migrated didn't have data (and hence the migration process was not triggered). If that was the case, you can try with the following steps:

  1. Clean up your testing device by removing your installed app.

  2. Switch your model to the original version (v1), then use Xcode to run your app on the device.

  3. Add some data, and then stop running your app.

  4. Switch your model to the current model version (v2), and then run your app again.

At step 4, the migration process should be triggered, which hopefully triggers the crash you described.

If you can reproduce the issue with your Xcode build but still need help to figure out the root cause, please upload a minima project that contains only the code relevant to the issue, with detailed steps to reproduce the issue. Folks here may be able to help more effectively.

If the above steps don't help, and the issue still happens only in your TestFlight build, you will need to capture and look into a sysdiagnose. For how to do so, see Using a Sysdiagnose Log to Debug a Hard-to-Reproduce Problem.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Hello, since almost all my App data comes from HealthKit, I can't read the data in the Xcode simulator. Is there any way to synchronize the iPhone data to the simulator to help me test and find the cause of the problem?

Also, I have tried testing by directly linking the iPhone through Xcode, and there are no errors.

I caught the error on the real device through other methods, as follows:

error: SwiftData.SwiftDataError(_error: SwiftData.SwiftDataError._Error.loadIssueModelContainer)

But there is no more detailed information.

I tried using sysdiagnose but it was too hard for me. This is how I caught the error:

import SwiftUI
import HealthKit
import SwiftData
import os.log



@main
struct MeApp: App {
    @StateObject var viewModel = ViewModel()
    @StateObject private var entitlementManager: EntitlementManager
    @StateObject private var purchaseManager: PurchaseManager
    @StateObject var sessionManager = SessionManager()
 
    let colors: [Color] = [ .introGreen, .introBlue]

    @State var sharedModelContainer: ModelContainer?
   
    @State var crashInfo: String = "No Crash"
    
    // 初始化方法
    init() {
        let entitlementManager = EntitlementManager()
        let purchaseManager = PurchaseManager(entitlementManager: entitlementManager)
        self._entitlementManager = StateObject(wrappedValue: entitlementManager)
        self._purchaseManager = StateObject(wrappedValue: purchaseManager)
    }
    
    var body: some Scene {
        WindowGroup {
            VStack{
                if let container = sharedModelContainer{
                    ContentView()
                        .environmentObject(viewModel)
                        .environmentObject(queryData)
                        .environmentObject(ringsDataQuery)
                        .environmentObject(darkModeSettings)
                        .environmentObject(recordsQuery)
                        .environmentObject(sst)
                        .environmentObject(hrQuery)
                        .environmentObject(purchaseManager)
                        .environmentObject(entitlementManager)
                        .environmentObject(sessionManager)
                        .modelContainer(container)
                        .preferredColorScheme(darkModeSettings.isDarkModeEnabled ? .dark : nil)
                        .task {
                            await loadImage()
                            await loadImage2()
                            await purchaseManager.updatePurchasedProducts()
                            sessionManager.startSession()
                        }
                } else {
                    VStack{
                        Image("Failed")
                            .resizable()
                            .scaledToFit()
                            .frame(width: 100)
                        Text("Er...There is something wrong.")
                            .font(.system(size: 36))
                            .fontWeight(.bold)
                            .multilineTextAlignment(.center)
                            .foregroundStyle(LinearGradient(colors: colors, startPoint: .leading, endPoint: .trailing))
                        Text(crashInfo)
                            .font(.system(size: 12))
                            .padding(20)
                    }
                    .padding(.horizontal,20)
                    .preferredColorScheme(.dark)
                    .transition(AnyTransition.move(edge: .bottom).animation(.spring(duration: 1.5)))
                    .transition(AnyTransition.opacity.animation(.spring(duration: 1.5)))
                    
          
                }
            }
            .onAppear{
                let log = Logger(subsystem: "Kookybread.Me", category: "modelContainer")
                do {
                    let schema = Schema([
                        HealthDataList.self,
                        HealthDataStatistics.self,
                        HeartZoneData.self,
                        UserSettingTypeFor.self,
                        SleepDataSource.self,
                        SleepStagesData.self,
                        SportsDataStatistics.self,
                        TodayHealthData.self,
                        TodayRingData.self,
                        WorkoutDetailData.self,
                        WorkoutHeartZoneData.self,
                        WorkoutList.self,
                        WorkoutMaxMinRangeData.self,
                        WorkoutSegmentData.self,
                        WorkoutStatisticsForTarget.self,
                        WorkoutTargetData.self,
                        AvatarImage.self,
                        NickName.self
                    ])
                    
                    sharedModelContainer = try ModelContainer(
                        for: schema,
                        migrationPlan: MeMigrationPlan.self
                    )
                    print("ModelContainer initialized successfully")
                } catch {
                    log.error("Failed to initialize model container: \(error, privacy: .public)")
                    crashInfo = "Error: \(error.self)"
                }
            }
        }
    }
}

I have tried using ModelConfiguration to customize the location, and it seems that there is no error, but at the same time the widget cannot get the data.

let storeURL = URL.documentsDirectory.appending(path: "MeDataBase.sqlite")
let config = ModelConfiguration(url: storeURL)

I searched the Internet for a long time, and almost all of them are related to CloudKit, but I don't use anything related to CloudKit. Another thing is that I use some Codable structures and @Attribute(.unique) in the definition of some of my models. I wonder if this has any impact on this?

I tested in Xcode today and found that when the migration started, the NSStoreModelVersionHashesVersion of the previous version of the Schema seemed to always be "1.0.0". Although the Models corresponded to Schema.Version(3, 0, 0)

...
CoreData: annotation: Incompatible version schema for persistent store 'file:///private/var/mobile/Containers/Shared/AppGroup/48F39567-A54A-45A6-B51F-EBB5064E488C/Library/Application%20Support/default.store'.  store metadata = {
    NSPersistenceFrameworkVersion = 1345;
    NSStoreModelVersionChecksumKey = "m47Q+vHOr0gMrRL9ogWBvunjLYZwmfrt0PDX73TO/oA=";
    NSStoreModelVersionHashes =     {
        HealthDataList = {length = 32, bytes = 0x27b73968 fe755a8f 28dd4224 1692184d ... e7762ec3 66e4f0fa };
        HealthDataStatistics = {length = 32, bytes = 0x7b1c108d 3578e7df 22035d15 b4f2eadf ... 4e150ac4 337f17f8 };
        SleepDataSource = {length = 32, bytes = 0x293b98b8 9ea8499c f7c61923 afd393a3 ... 7c034d3b d8928042 };
        SportsDataStatistics = {length = 32, bytes = 0x0b67e997 dddfddbd 5b53cd40 c58993f5 ... 77064cd6 b53b1b1b };
        TodayHealthData = {length = 32, bytes = 0xfb6de2ab 2fd504e6 88989a57 bb77a1a2 ... 56c03fca 0973076e };
        TodayRingData = {length = 32, bytes = 0x2c0a63ed 7babcb9a d206cd3e 62a35ca8 ... ee3ca37a 14b3b9ce };
        UserSettingTypeFor = {length = 32, bytes = 0xdc8b892a b46ce5b3 707263dd ba511389 ... d7899956 9e8d5890 };
        WorkoutList = {length = 32, bytes = 0xe510f3ad 374714f1 2845e983 d1b53a9b ... 4761cabf 477ac79b };
        WorkoutStatisticsForTarget = {length = 32, bytes = 0xe4e7fef0 daf11afe b9601a0f 5374446f ... 06e0be97 eccf372b };
        WorkoutTargetData = {length = 32, bytes = 0x4f636c57 b8de4f32 5be80c23 1bb882a1 ... 655be445 20d96f1a };
    };
    NSStoreModelVersionHashesDigest = "Qq+tCNnYs1Cq87uFu98DSiOor6rB8Noz6mfrEGibCFWNe0Y3uRqyok4sbz4LVhYpHUl3tgw9jRw6rrm6iimpxA==";
    NSStoreModelVersionHashesVersion = 3;
    NSStoreModelVersionIdentifiers =     (
        "1.0.0"
    );
    NSStoreType = SQLite;
    NSStoreUUID = "4DB70733-02E9-4BD7-8037-05AE5EE3DEDA";
    "_NSAutoVacuumLevel" = 2;
}
...

I continued to test other versions. Before and after the migration, ModelVersionIdentifiers was always "1.0.0"

 init() {       
        let schema = Schema([
            WorkoutList.self,
            HealthDataStatistics.self,
            SportsDataStatistics.self,
            UserSettingTypeFor.self,
            TodayRingData.self,
            TodayHealthData.self,
            SleepDataSource.self,
            WorkoutTargetData.self,
            WorkoutStatisticsForTarget.self,
            HealthDataList.self,
            SleepStagesData.self,
            WorkoutDetailData.self,
            HeartZoneData.self,
            WorkoutSegmentData.self,
            WorkoutMaxMinRangeData.self,
            WorkoutHeartZoneData.self
        ])
        
        do {
            container = try ModelContainer(
                for: schema,
                migrationPlan: MeMigrationPlan.self
            )
            print("ModelContainer initialized successfully")
            
            print("container \(container.self)")
            print("containerVersion \(container.schema.version)")
            print("containerEncodingVersion \(container.schema.encodingVersion)")
            print("containerMigrationPlan \(container.migrationPlan)")
            print("containerConfigurations \(container.configurations)")
        } catch {
            print("Error initializing model container: \(error)")
            fatalError("Failed to initialize model container.")
        }
    }

Log output after migration:

ModelContainer initialized successfully
container SwiftData.ModelContainer
containerVersion 1.0.0
containerEncodingVersion 1.0.0
containerMigrationPlan Optional(Me.MeMigrationPlan)
containerConfigurations [ModelConfiguration
name: default
url: file:///private/var/mobile/Containers/Shared/AppGroup/0D641150-923C-4A26-B908-1FCEA6012721/Library/Application%20Support/default.store
allowsSave: true
isStoredInMemoryOnly: false
cloudKitDatabase: CloudKitDatabase(_automatic: true, _none: false, _privateDBName: nil)
cloudKitContainerIdentifier: nil
groupContainer: GroupContainer(_automatic: true, _none: false, _identifier: nil)
groupAppContainerIdentifier: Optional("group.XXXXXXXXX.kookybread.me")]
error: SwiftData.SwiftDataError(_error: SwiftData.SwiftDataError._Error.loadIssueModelContainer)

Did you try to log error.localizedDescription, which I believe will contain the detailed error message?

I'd also suggest that you add -com.apple.CoreData.MigrationDebug 1 as a launch argument to your project, which tells the framework to log more information about the migration process.

If you catch the error when running your project with Xcode, most likely, there will have something like "CoreData: error: ..." in the Xcode console.

If you have a minimal project that can reproduce the issue, you can share it here as an attachment, with detailed steps. I'd give it a try once catching a chance. Without anything that indicates the reason of the error, it will be hard to go further.

Regarding that ModelVersionIdentifiers doesn't change before and after the migration, it may be that the framework doesn't update the metadata of the store. I don't think that matters, and believe you an ignore it.

I just tried logging an error.localizedDescription in TestFlight and got :

Could not create ModelContainer: The operation couldn’t be completed.

I also tried adding -com.apple.CoreData.MigrationDebug 1 to the launch arguments, but there was no "CoreData: error: ..." in the Xcode console.

I created a new project and copied the MigraitonPlan-related code from my code into it and uploaded it to Github.

TestDemo

This demo seems to report an error in xcode

Could not create ModelContainer: The operation couldn’t be completed.

But I checked it several times and couldn’t find the problem. In addition, when I run my app through xcode, there is no such error.

Crash by SwiftData MigarionPlan
 
 
Q