mirror of
https://github.com/samsonjs/Advanced-NSOperations.git
synced 2026-03-25 08:25:47 +00:00
136 lines
5.3 KiB
Swift
136 lines
5.3 KiB
Swift
/*
|
||
Copyright (C) 2015 Apple Inc. All Rights Reserved.
|
||
See LICENSE.txt for this sample’s licensing information
|
||
|
||
Abstract:
|
||
This file contains the code to create the Core Data stack.
|
||
*/
|
||
|
||
import CoreData
|
||
|
||
/**
|
||
An `EarthquakeOperation` subclass that loads the Core Data stack. If this operation fails,
|
||
it will produce an `AlertOperation` that will offer to retry the operation.
|
||
*/
|
||
class LoadModelOperation: EarthquakeOperation {
|
||
// MARK: Properties
|
||
|
||
let loadHandler: (NSManagedObjectContext) -> Void
|
||
|
||
// MARK: Initialization
|
||
|
||
init(loadHandler: @escaping (NSManagedObjectContext) -> Void) {
|
||
self.loadHandler = loadHandler
|
||
|
||
super.init()
|
||
|
||
// We only want one of these going at a time.
|
||
addCondition(condition: MutuallyExclusive<LoadModelOperation>())
|
||
}
|
||
|
||
override func execute() {
|
||
/*
|
||
We're not going to handle catching the error here, because if we can't
|
||
get the Caches directory, then your entire sandbox is broken and
|
||
there's nothing we can possibly do to fix it.
|
||
*/
|
||
let cachesFolder = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||
|
||
let storeURL = cachesFolder.appendingPathComponent("earthquakes.sqlite")
|
||
|
||
/*
|
||
Force unwrap this model, because this would only fail if we haven't
|
||
included the xcdatamodel in our app resources. If we forgot that step,
|
||
we deserve to crash. Plus, there's really no easy way to recover from
|
||
a missing model without reconstructing it programmatically
|
||
*/
|
||
let model = NSManagedObjectModel.mergedModel(from: nil)!
|
||
|
||
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
|
||
|
||
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
|
||
context.persistentStoreCoordinator = persistentStoreCoordinator
|
||
|
||
var error = createStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: storeURL)
|
||
|
||
if persistentStoreCoordinator.persistentStores.isEmpty {
|
||
/*
|
||
Our persistent store does not contain irreplaceable data (which
|
||
is why it's in the Caches folder). If we fail to add it, we can
|
||
delete it and try again.
|
||
*/
|
||
destroyStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: storeURL)
|
||
error = createStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: storeURL)
|
||
}
|
||
|
||
if persistentStoreCoordinator.persistentStores.isEmpty {
|
||
print("Error creating SQLite store: \(String(describing: error)).")
|
||
print("Falling back to `.InMemory` store.")
|
||
error = createStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: nil, type: NSInMemoryStoreType)
|
||
}
|
||
|
||
if !persistentStoreCoordinator.persistentStores.isEmpty {
|
||
loadHandler(context)
|
||
error = nil
|
||
}
|
||
|
||
finishWithError(error: error)
|
||
}
|
||
|
||
private func createStore(persistentStoreCoordinator: NSPersistentStoreCoordinator, atURL URL: URL?, type: String = NSSQLiteStoreType) -> NSError? {
|
||
var error: NSError?
|
||
do {
|
||
let _ = try persistentStoreCoordinator.addPersistentStore(ofType: type, configurationName: nil, at: URL as URL?, options: nil)
|
||
}
|
||
catch let storeError as NSError {
|
||
error = storeError
|
||
}
|
||
|
||
return error
|
||
}
|
||
|
||
private func destroyStore(persistentStoreCoordinator: NSPersistentStoreCoordinator, atURL URL: URL, type: String = NSSQLiteStoreType) {
|
||
do {
|
||
let _ = try persistentStoreCoordinator.destroyPersistentStore(at: URL as URL, ofType: type, options: nil)
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
override func finished(errors: [NSError]) {
|
||
guard let firstError = errors.first, userInitiated else { return }
|
||
|
||
/*
|
||
We failed to load the model on a user initiated operation try and present
|
||
an error.
|
||
*/
|
||
|
||
let alert = AlertOperation()
|
||
|
||
alert.title = "Unable to load database"
|
||
|
||
alert.message = "An error occurred while loading the database. \(firstError.localizedDescription). Please try again later."
|
||
|
||
// No custom action for this button.
|
||
alert.addAction(title: "Retry Later", style: .cancel)
|
||
|
||
// Declare this as a local variable to avoid capturing self in the closure below.
|
||
let handler = loadHandler
|
||
|
||
/*
|
||
For this operation, the `loadHandler` is only ever invoked if there are
|
||
no errors, so if we get to this point we know that it was not executed.
|
||
This means that we can offer to the user to try loading the model again,
|
||
simply by creating a new copy of the operation and giving it the same
|
||
loadHandler.
|
||
*/
|
||
alert.addAction(title: "Retry Now") { alertOperation in
|
||
let retryOperation = LoadModelOperation(loadHandler: handler)
|
||
|
||
retryOperation.userInitiated = true
|
||
|
||
alertOperation.produceOperation(operation: retryOperation)
|
||
}
|
||
|
||
produceOperation(operation: alert)
|
||
}
|
||
}
|