In the past few weeks, about Coredata, I added the database backup function. After going online, I received feedback from users that the app opened with a white screen and could not be started normally.
By investigating the reason why the App cannot be started, it is that there is no callback after loadPersistentStores is executed.
This problem has never been reported by users before, until I added the code for data backup, so I suspect it has something to do with data backup.
Below is the code for data backup
func backupPersistentStore(atIndex index: Int, fileName: String) throws -> TemporaryFile {
precondition(persistentStores.indices.contains(index), "Index \(index) doesn't exist in persistentStores array")
let sourceStore = persistentStores[index]
let backupCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
let intermediateStoreOptions = (sourceStore.options ?? [:])
.merging([NSReadOnlyPersistentStoreOption: true],
uniquingKeysWith: { $1 })
let intermediateStore = try backupCoordinator.addPersistentStore(
ofType: sourceStore.type,
configurationName: sourceStore.configurationName,
at: sourceStore.url,
options: intermediateStoreOptions
)
let backupStoreOptions: [AnyHashable: Any] = [
NSReadOnlyPersistentStoreOption: true,
NSSQLitePragmasOption: ["journal_mode": "DELETE"],
// Minimize file size
NSSQLiteManualVacuumOption: true,
]
let backupFilename = fileName + ".sqlite"
let backupFile = try TemporaryFile(creatingTempDirectoryForFilename: backupFilename)
try backupCoordinator.migratePersistentStore(intermediateStore, to: backupFile.fileURL, options: backupStoreOptions, withType: NSSQLiteStoreType)
return backupFile
}
When to back up
- sceneDidEnterBackground use Operation with UIApplication.shared.beginBackgroundTask
class BackUpOpeartion: Operation {
override func main() {
LocalBackupManager.shared.backupSync()
}
static func canHandler() -> Bool {
let backupStrategy = LocalUserDefaults.standard.backupUiModel
let lastBackUpDate = LocalUserDefaults.standard.lastBackUpDate
let now = Date()
let sixHour: TimeInterval
#if DEBUG
sixHour = TimeInterval(0.15 * 60 * 60)
#else
sixHour = TimeInterval(backupStrategy.backupFrequency) // 6h
#endif
let backupDate = lastBackUpDate + sixHour
// Clean the database at most once per week.
guard now > backupDate else {
"It's not time to backup".debugLog()
return false
}
return true
}
static func doWhenEnterBackground() {
if !canHandler() {
return
}
var bgtToken = UIBackgroundTaskIdentifier.invalid
bgtToken = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(bgtToken)
})
guard bgtToken != .invalid else{
return
}
DispatchQueue.global(qos: .utility).async {
LocalBackupManager.shared.backupSync { succeed in
DispatchQueue.main.async {
if succeed {
LocalUserDefaults.standard.lastBackUpDate = Date()
}
UIApplication.shared.endBackgroundTask(bgtToken)
}
}
}
}
}
- BGProcessingTask in AppDelegate
func registerBackgroundTasks() {
BGTaskScheduler.shared.register(forTaskWithIdentifier: dbBackupId, using: nil) { task in
self.backupDb(task: task as! BGProcessingTask)
}
}
func scheduleDbBackup() {
if !BackUpOpeartion.canHandler() {
return
}
let request = BGProcessingTaskRequest(identifier: dbBackupId)
request.requiresNetworkConnectivity = false
request.requiresExternalPower = false
do {
try BGTaskScheduler.shared.submit(request)
"submit DbBackup".debugLog()
} catch {
print("Could not schedule app refresh: \(error)")
}
}
func backupDb(task: BGProcessingTask) {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
let operation = BackUpOpeartion()
task.expirationHandler = {
queue.cancelAllOperations()
}
operation.completionBlock = {
let success = !operation.isCancelled
if success {
LocalUserDefaults.standard.lastBackUpDate = Date()
}
task.setTaskCompleted(success: success)
}
}
Since before the online backup, there is no feedback about the white screen of the app failing to start, so I feel it is related to the backup
I don't know under what circumstances it would cause CoreDataStack loadPersistentStores no completionHandler callback
can someone help me? Thanks!