Actualmente, nuestra aplicación proporciona una funcionalidad de copia de seguridad al usuario, donde puede realizar una copia de seguridad instantánea de la base de datos de la aplicación.
Usamos migratePersistentStore para lograr dicha funcionalidad.
Después de ejecutar migratePersistentStore , ejecutaremos otras operaciones de E/S. Algunas de las operaciones de E/S pueden fallar. En tal caso, necesitamos eliminar completamente la base de datos respaldada.
Sin embargo, notamos que es muy difícil eliminar la base de datos respaldada.
Notamos que, en cierto punto, CoreData volverá a crear el archivo SQLite, aunque hayamos destroyPersistentStore y FileManager.default.removeItem .
Aquí está nuestro fragmento de código.
public static func cloneXXXDatabase(dstUrl: URL, directory: Directory, cloneTrash: Bool) -> Bool { // Current already opened app database. let coreDataStack = CoreDataStack.INSTANCE guard let srcUrl = coreDataStack.persistentContainer.persistentStoreDescriptions.first?.url else { return false } // New destination to backup current app database. let coreDataNamedStack = CoreDataNamedStack(srcUrl) // Have a new NSPersistentStoreCoordinator solely for migration purpose. let psc = coreDataNamedStack.persistentContainer.persistentStoreCoordinator // Open the SQLite. Is it fine for 2 different NSPersistentStoreCoordinator to access 1 same SQLite? guard let srcStore = psc.persistentStore(for: srcUrl) else { return false } do { // Reference: https://www.avanderlee.com/swift/write-ahead-logging-wal/ // This is to ensure only 1 SQLite file is produced, without WAL & SHM. let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]] try psc.migratePersistentStore(srcStore, to: dstUrl, options: options, withType: NSSQLiteStoreType) } catch { error_log(error) return false } // ... // ... (some other I/O operations) // ... var somethingWentWrong = true if somethingWentWrong { do { try psc.destroyPersistentStore(at: dstUrl, ofType: NSSQLiteStoreType) if dstUrl.exists { try FileManager.default.removeItem(at: dstUrl) } print(">>>> DELETE \(dstUrl)") // // WARNING: Such SQLite removal code is not working, and I am not sure why?! // It seems that after returning from this function, CoreData will still re-create the backup destination // DB, by logging // // CoreData: annotation: Connecting to sqlite database file at ".../xxx.sqlite". // } catch { error_log(error) } } return true } ¿Tiene idea de cómo podemos eliminar limpiamente el archivo SQLite, que se genera a través migratePersistentStore ? destroyPersistentStore y FileManager.default.removeItem no parecen funcionar.
Gracias.
Aquí está el código de CoreDataStack (pila de datos central para la aplicación principal) y CoreDataNamedStack (pila de datos central que apunta al destino de la copia de seguridad)
class CoreDataStack { static let INSTANCE = CoreDataStack() private init() { } private(set) lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "xxx", managedObjectModel: NSManagedObjectModel.wenote) container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { // This is a serious fatal error. We will just simply terminate the app, rather than using error_log. fatalError("Unresolved error \(error), \(error.userInfo)") } }) // So that when backgroundContext write to persistent store, container.viewContext will retrieve update from // persistent store. container.viewContext.automaticallyMergesChangesFromParent = true return container }() } class CoreDataNamedStack: CoreDataStackable { let url: URL init(_ url: URL) { self.url = url } private(set) lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "xxx", managedObjectModel: NSManagedObjectModel.wenote) let storeDescription = NSPersistentStoreDescription(url: url) container.persistentStoreDescriptions = [storeDescription] container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { // This is a serious fatal error. We will just simply terminate the app, rather than using error_log. fatalError("Unresolved error \(error), \(error.userInfo)") } }) // So that when backgroundContext write to persistent store, container.viewContext will retrieve update from // persistent store. container.viewContext.automaticallyMergesChangesFromParent = true return container }() }En lugar de ejecutar el siguiente código de eliminación inmediatamente
var somethingWentWrong = true if somethingWentWrong { do { try psc.destroyPersistentStore(at: dstUrl, ofType: NSSQLiteStoreType) if dstUrl.exists { try FileManager.default.removeItem(at: dstUrl) } print(">>>> DELETE \(dstUrl)") } catch { error_log(error) } }Si nos demoramos unos segundos antes de ejecutar código de borrado.
var somethingWentWrong = true DispatchQueue.main.asyncAfter(deadline: .now() + 2) { if somethingWentWrong { do { try psc.destroyPersistentStore(at: dstUrl, ofType: NSSQLiteStoreType) if dstUrl.exists { try FileManager.default.removeItem(at: dstUrl) } print(">>>> DELETE \(dstUrl)") } catch { error_log(error) } } }Entonces dicha eliminación tendrá éxito.
Supongo que CoreData tiene un subproceso de fondo que confirma cambios en el disco. Si la eliminación ocurre antes de que se confirmen los cambios en el disco, se volverá a crear el archivo de la base de datos.
¿Puedo saber cuál es la forma adecuada de resolver esto, sin tener un bloque de código retrasado arbitrario/aleatorio?
Observaciones que he anotado de sus fragmentos de código:
NSPersistentStoreCoordinator . Documentación de ApplemigratePersistentStore() , por lo que debe verificar si la migración completa se completó antes de eliminar la base de datos respaldada. Para hacerlo puedes comprobar el estado de la migración y destroyPersistentStore ple:
let migrationResult = try psc.migratePersistentStore(srcStore, to: dstUrl, options: options, withType: NSSQLiteStoreType) if migrationResult { do { try psc.destroyPersistentStore(at: dstUrl, ofType: NSSQLiteStoreType) if dstUrl.exists { try FileManager.default.removeItem(at: dstUrl) } print(">>>> DELETE \(dstUrl)") // // WARNING: Such SQLite removal code is not working, and I am not sure why?! // It seems that after returning from this function, CoreData will still re-create the backup destination // DB, by logging // // CoreData: annotation: Connecting to sqlite database file at ".../xxx.sqlite". // } catch { error_log(error) } }