For anyone looking for a solution. I fired a TSI, and now figured out what is the problem.
Reply from Apple Developer Technical Support:
When sharing a Core Data store among multiple processes, like your main app and extensions, errors can happen when multiple processes try to load the store almost at the same time, because both processes triggers the migration, which leads to a crash or data consistency. In that case, I don’t see a solution, other than avoiding multiple processes loading the store at the same time.
Then, how to avoid multiple processes loading the store at the same time?
I normally consider two options:
Your extensions load the store with the readonly mode, and rely on the main app to change or migrate the store.
If that is not possible, coordinate the processes with your own mechanism. For example, KVO + sharing UserDefaults (UserDefaults(suiteName: <App Group ID>)) to monitor changes from other process in an app group.
What I have Done to avoid this problem:
Widgets(iOS 14+):
Load the store with the readonly mode.
#if APP_WIDGET
description.setOption(true as NSNumber, forKey: NSReadOnlyPersistentStoreOption)
#endif
Today extension(iOS 13 +)
Check if migration is required before load persistent stores. If is, load persistent stores until migration is not required. If not, load persistent stores normally.
...
if container.isNeedMigration() {
Logger.info("Need Migration")
#if TODAY_EXTENSION
while container.isNeedMigration() {
do {
sleep(3)
}
}
#endif
}
container.loadPersistentStores
...
....
....
extension NSPersistentContainer {
var url: URL? {
let url: URL? = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.xxxx.xxxx.xxxx")
return url?.appendingPathComponent("xxxxx.sqlite")
}
}
extension NSPersistentContainer {
func isNeedMigration() -> Bool {
guard let url = self.url else { return false }
if FileManager.default.fileExists(atPath: url.path) {
do {
let sourceMetadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: url, options: nil)
let destinationModel = persistentStoreCoordinator.managedObjectModel
return !destinationModel.isConfiguration(withName: nil, compatibleWithStoreMetadata: sourceMetadata)
} catch {
let e = error
Logger.info("error when check xmode compatibile", e)
}
}
return false
}
}
Hope someone find a better way to avoid multiple processes loading the store at the same time.