WIP: StorageInterface methods

This commit is contained in:
John Rommel Estropia
2016-03-07 07:23:44 +09:00
parent 3c514830d9
commit 75a4ebb49b
12 changed files with 589 additions and 426 deletions

View File

@@ -16,7 +16,7 @@
82BA18A11C4BBD1D00A0916E /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; }; 82BA18A11C4BBD1D00A0916E /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; };
82BA18A21C4BBD1D00A0916E /* NSError+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */; }; 82BA18A21C4BBD1D00A0916E /* NSError+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */; };
82BA18A31C4BBD2200A0916E /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; }; 82BA18A31C4BBD2200A0916E /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; };
82BA18A41C4BBD2200A0916E /* PersistentStoreResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDE1AFF84500064E85B /* PersistentStoreResult.swift */; }; 82BA18A41C4BBD2200A0916E /* SetupResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDE1AFF84500064E85B /* SetupResult.swift */; };
82BA18A51C4BBD2200A0916E /* CoreStore+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */; }; 82BA18A51C4BBD2200A0916E /* CoreStore+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */; };
82BA18A61C4BBD2900A0916E /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE31AFF84610064E85B /* DefaultLogger.swift */; }; 82BA18A61C4BBD2900A0916E /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE31AFF84610064E85B /* DefaultLogger.swift */; };
82BA18A71C4BBD2900A0916E /* CoreStore+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE41AFF84610064E85B /* CoreStore+Logging.swift */; }; 82BA18A71C4BBD2900A0916E /* CoreStore+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE41AFF84610064E85B /* CoreStore+Logging.swift */; };
@@ -94,7 +94,7 @@
B52DD1941BE1F92500949AFE /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; }; B52DD1941BE1F92500949AFE /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; };
B52DD1951BE1F92500949AFE /* NSError+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */; }; B52DD1951BE1F92500949AFE /* NSError+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */; };
B52DD1961BE1F92500949AFE /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; }; B52DD1961BE1F92500949AFE /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; };
B52DD1971BE1F92500949AFE /* PersistentStoreResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDE1AFF84500064E85B /* PersistentStoreResult.swift */; }; B52DD1971BE1F92500949AFE /* SetupResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDE1AFF84500064E85B /* SetupResult.swift */; };
B52DD1981BE1F92500949AFE /* CoreStore+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */; }; B52DD1981BE1F92500949AFE /* CoreStore+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */; };
B52DD1991BE1F92800949AFE /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE31AFF84610064E85B /* DefaultLogger.swift */; }; B52DD1991BE1F92800949AFE /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE31AFF84610064E85B /* DefaultLogger.swift */; };
B52DD19A1BE1F92800949AFE /* CoreStore+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE41AFF84610064E85B /* CoreStore+Logging.swift */; }; B52DD19A1BE1F92800949AFE /* CoreStore+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE41AFF84610064E85B /* CoreStore+Logging.swift */; };
@@ -152,7 +152,7 @@
B563217F1BD65216006C9394 /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; }; B563217F1BD65216006C9394 /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; };
B56321801BD65216006C9394 /* NSError+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */; }; B56321801BD65216006C9394 /* NSError+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */; };
B56321811BD65216006C9394 /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; }; B56321811BD65216006C9394 /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; };
B56321821BD65216006C9394 /* PersistentStoreResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDE1AFF84500064E85B /* PersistentStoreResult.swift */; }; B56321821BD65216006C9394 /* SetupResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDE1AFF84500064E85B /* SetupResult.swift */; };
B56321831BD65216006C9394 /* CoreStore+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */; }; B56321831BD65216006C9394 /* CoreStore+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */; };
B56321841BD65216006C9394 /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE31AFF84610064E85B /* DefaultLogger.swift */; }; B56321841BD65216006C9394 /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE31AFF84610064E85B /* DefaultLogger.swift */; };
B56321851BD65216006C9394 /* CoreStore+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE41AFF84610064E85B /* CoreStore+Logging.swift */; }; B56321851BD65216006C9394 /* CoreStore+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE41AFF84610064E85B /* CoreStore+Logging.swift */; };
@@ -227,7 +227,7 @@
B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */; }; B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */; };
B5E834BB1B7691F3001D3D50 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834BA1B7691F3001D3D50 /* Functions.swift */; }; B5E834BB1B7691F3001D3D50 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834BA1B7691F3001D3D50 /* Functions.swift */; };
B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; }; B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; };
B5E84EE11AFF84500064E85B /* PersistentStoreResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDE1AFF84500064E85B /* PersistentStoreResult.swift */; }; B5E84EE11AFF84500064E85B /* SetupResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDE1AFF84500064E85B /* SetupResult.swift */; };
B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE31AFF84610064E85B /* DefaultLogger.swift */; }; B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE31AFF84610064E85B /* DefaultLogger.swift */; };
B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE41AFF84610064E85B /* CoreStore+Logging.swift */; }; B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE41AFF84610064E85B /* CoreStore+Logging.swift */; };
B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE51AFF84610064E85B /* CoreStoreLogger.swift */; }; B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE51AFF84610064E85B /* CoreStoreLogger.swift */; };
@@ -356,7 +356,7 @@
B5E84ED81AFF82360064E85B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; B5E84ED81AFF82360064E85B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; };
B5E84ED91AFF82360064E85B /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = SOURCE_ROOT; }; B5E84ED91AFF82360064E85B /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = SOURCE_ROOT; };
B5E84EDB1AFF84500064E85B /* DataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataStack.swift; sourceTree = "<group>"; }; B5E84EDB1AFF84500064E85B /* DataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataStack.swift; sourceTree = "<group>"; };
B5E84EDE1AFF84500064E85B /* PersistentStoreResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentStoreResult.swift; sourceTree = "<group>"; }; B5E84EDE1AFF84500064E85B /* SetupResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupResult.swift; sourceTree = "<group>"; };
B5E84EE31AFF84610064E85B /* DefaultLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultLogger.swift; sourceTree = "<group>"; }; B5E84EE31AFF84610064E85B /* DefaultLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultLogger.swift; sourceTree = "<group>"; };
B5E84EE41AFF84610064E85B /* CoreStore+Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Logging.swift"; sourceTree = "<group>"; }; B5E84EE41AFF84610064E85B /* CoreStore+Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Logging.swift"; sourceTree = "<group>"; };
B5E84EE51AFF84610064E85B /* CoreStoreLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreLogger.swift; sourceTree = "<group>"; }; B5E84EE51AFF84610064E85B /* CoreStoreLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreLogger.swift; sourceTree = "<group>"; };
@@ -596,7 +596,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B5E84EDB1AFF84500064E85B /* DataStack.swift */, B5E84EDB1AFF84500064E85B /* DataStack.swift */,
B5E84EDE1AFF84500064E85B /* PersistentStoreResult.swift */, B5E84EDE1AFF84500064E85B /* SetupResult.swift */,
B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */, B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */,
B5FE4DA01C84818B00FA6A91 /* PersistentStores */, B5FE4DA01C84818B00FA6A91 /* PersistentStores */,
); );
@@ -1021,7 +1021,7 @@
B5FE4DA21C8481E100FA6A91 /* StorageInterface.swift in Sources */, B5FE4DA21C8481E100FA6A91 /* StorageInterface.swift in Sources */,
B50392F91C478FF3009900CA /* NSManagedObject+Transaction.swift in Sources */, B50392F91C478FF3009900CA /* NSManagedObject+Transaction.swift in Sources */,
B5202CFA1C04688100DED140 /* NSFetchedResultsController+Convenience.swift in Sources */, B5202CFA1C04688100DED140 /* NSFetchedResultsController+Convenience.swift in Sources */,
B5E84EE11AFF84500064E85B /* PersistentStoreResult.swift in Sources */, B5E84EE11AFF84500064E85B /* SetupResult.swift in Sources */,
B5E84F251AFF84860064E85B /* ObjectObserver.swift in Sources */, B5E84F251AFF84860064E85B /* ObjectObserver.swift in Sources */,
B5E84F2F1AFF849C0064E85B /* NotificationObserver.swift in Sources */, B5E84F2F1AFF849C0064E85B /* NotificationObserver.swift in Sources */,
B5F1DA8D1B9AA97D007C5CBB /* ImportableObject.swift in Sources */, B5F1DA8D1B9AA97D007C5CBB /* ImportableObject.swift in Sources */,
@@ -1114,7 +1114,7 @@
82BA18C61C4BBD5900A0916E /* DataStack+Migration.swift in Sources */, 82BA18C61C4BBD5900A0916E /* DataStack+Migration.swift in Sources */,
82BA18CD1C4BBD7100A0916E /* AssociatedObjects.swift in Sources */, 82BA18CD1C4BBD7100A0916E /* AssociatedObjects.swift in Sources */,
82BA18B71C4BBD3F00A0916E /* CoreStore+Querying.swift in Sources */, 82BA18B71C4BBD3F00A0916E /* CoreStore+Querying.swift in Sources */,
82BA18A41C4BBD2200A0916E /* PersistentStoreResult.swift in Sources */, 82BA18A41C4BBD2200A0916E /* SetupResult.swift in Sources */,
82BA18AA1C4BBD3100A0916E /* BaseDataTransaction.swift in Sources */, 82BA18AA1C4BBD3100A0916E /* BaseDataTransaction.swift in Sources */,
82BA18A91C4BBD3100A0916E /* Into.swift in Sources */, 82BA18A91C4BBD3100A0916E /* Into.swift in Sources */,
82BA18D11C4BBD7100A0916E /* NotificationObserver.swift in Sources */, 82BA18D11C4BBD7100A0916E /* NotificationObserver.swift in Sources */,
@@ -1178,7 +1178,7 @@
B52DD1C41BE1F94600949AFE /* NSFileManager+Setup.swift in Sources */, B52DD1C41BE1F94600949AFE /* NSFileManager+Setup.swift in Sources */,
B52DD1AC1BE1F93900949AFE /* Select.swift in Sources */, B52DD1AC1BE1F93900949AFE /* Select.swift in Sources */,
B5FE4DAF1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */, B5FE4DAF1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */,
B52DD1971BE1F92500949AFE /* PersistentStoreResult.swift in Sources */, B52DD1971BE1F92500949AFE /* SetupResult.swift in Sources */,
B52DD1C71BE1F94600949AFE /* NSManagedObjectContext+Querying.swift in Sources */, B52DD1C71BE1F94600949AFE /* NSManagedObjectContext+Querying.swift in Sources */,
B52DD1C81BE1F94600949AFE /* NSManagedObjectContext+Setup.swift in Sources */, B52DD1C81BE1F94600949AFE /* NSManagedObjectContext+Setup.swift in Sources */,
B52DD1C31BE1F94600949AFE /* NotificationObserver.swift in Sources */, B52DD1C31BE1F94600949AFE /* NotificationObserver.swift in Sources */,
@@ -1256,7 +1256,7 @@
B50392FA1C47963F009900CA /* NSManagedObject+Transaction.swift in Sources */, B50392FA1C47963F009900CA /* NSManagedObject+Transaction.swift in Sources */,
B56321971BD65216006C9394 /* Select.swift in Sources */, B56321971BD65216006C9394 /* Select.swift in Sources */,
B56321AB1BD6521C006C9394 /* FetchedResultsControllerDelegate.swift in Sources */, B56321AB1BD6521C006C9394 /* FetchedResultsControllerDelegate.swift in Sources */,
B56321821BD65216006C9394 /* PersistentStoreResult.swift in Sources */, B56321821BD65216006C9394 /* SetupResult.swift in Sources */,
B563219C1BD65216006C9394 /* SectionBy.swift in Sources */, B563219C1BD65216006C9394 /* SectionBy.swift in Sources */,
B56321B21BD6521C006C9394 /* NSManagedObjectContext+Querying.swift in Sources */, B56321B21BD6521C006C9394 /* NSManagedObjectContext+Querying.swift in Sources */,
B5FE4DA41C8481E100FA6A91 /* StorageInterface.swift in Sources */, B5FE4DA41C8481E100FA6A91 /* StorageInterface.swift in Sources */,

View File

@@ -33,6 +33,31 @@ internal extension NSPersistentStoreCoordinator {
// MARK: Internal // MARK: Internal
internal func performBlockAndWait<T>(block: () throws -> T) throws -> T {
var result: T?
var closureError: ErrorType?
self.performBlockAndWait {
do {
result = try block()
}
catch {
closureError = error
}
}
if let result = result {
return result
}
throw closureError!
}
internal func addPersistentStoreSynchronously(storeType: String, configuration: String?, URL storeURL: NSURL?, options: [NSObject : AnyObject]?) throws -> NSPersistentStore { internal func addPersistentStoreSynchronously(storeType: String, configuration: String?, URL storeURL: NSURL?, options: [NSObject : AnyObject]?) throws -> NSPersistentStore {
var store: NSPersistentStore? var store: NSPersistentStore?

View File

@@ -34,16 +34,46 @@ import CoreData
public extension CoreStore { public extension CoreStore {
/** public static func addStorage<T: StorageInterface where T: DefaultInitializableStore>(storeType: T.Type, completion: (SetupResult<T>) -> Void) throws -> NSProgress? {
Asynchronously adds to the `defaultStack` an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS). A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. return try self.defaultStack.addStorage(storeType.init(), completion: completion)
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. }
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false. public static func addStorage<T: StorageInterface>(storage: T, completion: (SetupResult<T>) -> Void) throws -> NSProgress? {
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required return try self.defaultStack.addStorage(storage, completion: completion)
}
public static func addStorage<T: LocalStorage where T: DefaultInitializableStore>(storeType: T.Type, completion: (SetupResult<T>) -> Void) throws -> NSProgress? {
return try self.defaultStack.addStorage(storeType.init(), completion: completion)
}
public static func addStorage<T: LocalStorage>(storage: T, completion: (SetupResult<T>) -> Void) throws -> NSProgress? {
return try self.defaultStack.addStorage(storage, completion: completion)
}
public static func upgradeStorageIfNeeded<T: LocalStorage>(storage: T, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.upgradeStorageIfNeeded(storage, completion: completion)
}
@warn_unused_result
public static func requiredMigrationsForStorage<T: LocalStorage>(storage: T) throws -> [MigrationType] {
return try self.defaultStack.requiredMigrationsForStorage(storage)
}
// MARK: Internal
/**
Deprecated. Use `addSQLiteStore(_:completion:)` by passing a `LegacySQLiteStore` instance.
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`.
*/ */
@available(*, deprecated=2.0.0, message="Use addSQLiteStore(_:completion:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.")
public static func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? { public static func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.addSQLiteStore( return try self.defaultStack.addSQLiteStore(
@@ -56,16 +86,12 @@ public extension CoreStore {
} }
/** /**
Asynchronously adds to the `defaultStack` an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.) Deprecated. Use `addSQLiteStore(_:completion:)` by passing a `LegacySQLiteStore` instance.
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. - Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/ */
public static func addSQLiteStore(fileURL fileURL: NSURL = DataStack.DeprecatedDefaults.defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? { @available(*, deprecated=2.0.0, message="Use addSQLiteStore(_:completion:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.")
public static func addSQLiteStore(fileURL fileURL: NSURL = LegacySQLiteStore.legacyDefaultFileURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.addSQLiteStore( return try self.defaultStack.addSQLiteStore(
fileURL: fileURL, fileURL: fileURL,
@@ -77,51 +103,43 @@ public extension CoreStore {
} }
/** /**
Using the `defaultStack`, migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack. Deprecated. Use `upgradeStorageIfNeeded(_:completion:)` by passing a `LegacySQLiteStore` instance.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS). - Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/ */
@available(*, deprecated=2.0.0, message="Use upgradeStorageIfNeeded(_:completion:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.")
public static func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? { public static func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.upgradeSQLiteStoreIfNeeded( return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
fileName: fileName, fileName: fileName,
configuration: configuration, configuration: configuration,
mappingModelBundles: mappingModelBundles, mappingModelBundles: mappingModelBundles ?? NSBundle.allBundles(),
completion: completion completion: completion
) )
} }
/** /**
Using the `defaultStack`, migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack. Deprecated. Use `upgradeStorageIfNeeded(_:completion:)` by passing a `LegacySQLiteStore` instance.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS). - Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/ */
public static func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = DataStack.DeprecatedDefaults.defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? { @available(*, deprecated=2.0.0, message="Use upgradeStorageIfNeeded(_:completion:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.")
public static func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = LegacySQLiteStore.legacyDefaultFileURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.upgradeSQLiteStoreIfNeeded( return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
fileURL: fileURL, fileURL: fileURL,
configuration: configuration, configuration: configuration,
mappingModelBundles: mappingModelBundles, mappingModelBundles: mappingModelBundles ?? NSBundle.allBundles(),
completion: completion completion: completion
) )
} }
/** /**
Using the `defaultStack`, checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred. Deprecated. Use `requiredMigrationsForStorage(_:)` by passing a `LegacySQLiteStore` instance.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS). - Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/ */
@available(*, deprecated=2.0.0, message="Use requiredMigrationsForStorage(_:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.")
@warn_unused_result @warn_unused_result
public static func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] { public static func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
@@ -133,15 +151,13 @@ public extension CoreStore {
} }
/** /**
Using the `defaultStack`, checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version. Deprecated. Use `requiredMigrationsForStorage(_:)` by passing a `LegacySQLiteStore` instance.
- parameter fileURL: the local file URL for the SQLite persistent store. - Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/ */
@available(*, deprecated=2.0.0, message="Use requiredMigrationsForStorage(_:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.")
@warn_unused_result @warn_unused_result
public static func requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = DataStack.DeprecatedDefaults.defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] { public static func requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = LegacySQLiteStore.legacyDefaultFileURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try self.defaultStack.requiredMigrationsForSQLiteStore( return try self.defaultStack.requiredMigrationsForSQLiteStore(
fileURL: fileURL, fileURL: fileURL,

View File

@@ -34,29 +34,51 @@ import CoreData
public extension DataStack { public extension DataStack {
/** public func addStorage<T: StorageInterface where T: DefaultInitializableStore>(storeType: T.Type, completion: (SetupResult<T>) -> Void) throws -> NSProgress? {
Asynchronously adds an in-memory store to the stack.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`. return try self.addStorage(storeType.init(), completion: completion)
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. }
*/
public func addInMemoryStore(configuration configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
self.coordinator.performBlock { public func addStorage<T: StorageInterface>(storage: T, completion: (SetupResult<T>) -> Void) throws -> NSProgress? {
// TODO: check
let coordinator = self.coordinator;
// if let persistentStore = coordinator.persistentStoreForURL(fileURL) {
//
// guard persistentStore.type == storage.dynamicType.storeType
// && persistentStore.configurationName == (storage.configuration ?? Into.defaultConfigurationName) else {
//
// let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
// CoreStore.handleError(
// error,
// "Failed to add SQLite \(typeName(storage)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
// )
// throw error
// }
//
// GCDQueue.Main.async {
//
// completion(SetupResult(storage))
// }
// return nil
// }
coordinator.performBlock {
do { do {
let store = try self.coordinator.addPersistentStoreWithType( let persistentStore = try coordinator.addPersistentStoreWithType(
NSInMemoryStoreType, storage.dynamicType.storeType,
configuration: configuration, configuration: storage.configuration,
URL: nil, URL: nil,
options: nil options: storage.storeOptions
) )
self.updateMetadataForPersistentStore(store) self.updateMetadataForPersistentStore(persistentStore)
storage.internalStore = persistentStore
GCDQueue.Main.async { GCDQueue.Main.async {
completion(PersistentStoreResult(store)) completion(SetupResult(storage))
} }
} }
catch { catch {
@@ -64,39 +86,22 @@ public extension DataStack {
let storeError = error as NSError let storeError = error as NSError
CoreStore.handleError( CoreStore.handleError(
storeError, storeError,
"Failed to add in-memory \(typeName(NSPersistentStore)) to the stack." "Failed to add \(typeName(storage)) to the stack."
) )
GCDQueue.Main.async { GCDQueue.Main.async {
completion(PersistentStoreResult(storeError)) completion(SetupResult(storeError))
} }
} }
} }
return nil
} }
/** public func addStorage<T: LocalStorage where T: DefaultInitializableStore>(storeType: T.Type, completion: (SetupResult<T>) -> Void) throws -> NSProgress? {
Asynchronously adds to the stack an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS). A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. return try self.addStorage(storeType.init(), completion: completion)
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.addSQLiteStore(
fileURL: DataStack.DeprecatedDefaults.defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
mappingModelBundles: mappingModelBundles,
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
completion: completion
)
} }
/** /**
@@ -109,98 +114,90 @@ public extension DataStack {
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously. - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/ */
public func addSQLiteStore(fileURL fileURL: NSURL = DataStack.DeprecatedDefaults.defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? { public func addStorage<T: LocalStorage>(storage: T, completion: (SetupResult<T>) -> Void) throws -> NSProgress? {
let fileURL = storage.fileURL
CoreStore.assert( CoreStore.assert(
fileURL.fileURL, fileURL.fileURL,
"The specified file URL for the SQLite store is invalid: \"\(fileURL)\"" "The specified URL for the \(typeName(storage)) is invalid: \"\(fileURL)\""
) )
// TODO: check
let coordinator = self.coordinator; let coordinator = self.coordinator;
if let store = coordinator.persistentStoreForURL(fileURL) { if let persistentStore = coordinator.persistentStoreForURL(fileURL) {
guard store.type == NSSQLiteStoreType guard persistentStore.type == storage.dynamicType.storeType
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) else { && persistentStore.configurationName == (storage.configuration ?? Into.defaultConfigurationName) else {
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL) let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
CoreStore.handleError( CoreStore.handleError(
error, error,
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists." "Failed to add \(typeName(storage)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
) )
throw error throw error
} }
GCDQueue.Main.async { GCDQueue.Main.async {
completion(PersistentStoreResult(store)) completion(SetupResult(storage))
} }
return nil return nil
} }
let fileManager = NSFileManager.defaultManager() let fileManager = NSFileManager.defaultManager()
_ = try? fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
do { do {
_ = try? fileManager.createDirectoryAtURL(
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( fileURL.URLByDeletingLastPathComponent!,
NSSQLiteStoreType, withIntermediateDirectories: true,
URL: fileURL, attributes: nil
options: self.optionsForSQLiteStore()
) )
return self.upgradeSQLiteStoreIfNeeded( let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
fileURL: fileURL, storage.dynamicType.storeType,
URL: fileURL,
options: storage.storeOptions
)
return self.upgradeStorageIfNeeded(
storage,
metadata: metadata, metadata: metadata,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: { (result) -> Void in completion: { (result) -> Void in
if case .Failure(let error) = result { if case .Failure(let error) = result {
if resetStoreOnModelMismatch && error.isCoreDataMigrationError { if storage.resetStoreOnModelMismatch && error.isCoreDataMigrationError {
fileManager.removeSQLiteStoreAtURL(fileURL) fileManager.removeSQLiteStoreAtURL(fileURL)
do { do {
let store = try self.addSQLiteStoreAndWait( try self.addStorageAndWait(storage)
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: false
)
GCDQueue.Main.async { GCDQueue.Main.async {
completion(PersistentStoreResult(store)) completion(SetupResult(storage))
} }
} }
catch { catch {
completion(PersistentStoreResult(error as NSError)) completion(SetupResult(error as NSError))
} }
return return
} }
completion(PersistentStoreResult(error)) completion(SetupResult(error))
return return
} }
do { do {
let store = try self.addSQLiteStoreAndWait( try self.addStorageAndWait(storage)
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: false
)
completion(PersistentStoreResult(store)) completion(SetupResult(storage))
} }
catch { catch {
completion(PersistentStoreResult(error as NSError)) completion(SetupResult(error as NSError))
} }
} }
) )
@@ -208,15 +205,11 @@ public extension DataStack {
catch let error as NSError catch let error as NSError
where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain { where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain {
let store = try self.addSQLiteStoreAndWait( try self.addStorageAndWait(storage)
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: false
)
GCDQueue.Main.async { GCDQueue.Main.async {
completion(PersistentStoreResult(store)) completion(SetupResult(storage))
} }
return nil return nil
} }
@@ -230,28 +223,6 @@ public extension DataStack {
} }
} }
/**
Migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.upgradeSQLiteStoreIfNeeded(
fileURL: DataStack.DeprecatedDefaults.defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/** /**
Migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack. Migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
@@ -261,58 +232,37 @@ public extension DataStack {
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. - parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/ */
public func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = DataStack.DeprecatedDefaults.defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? { public func upgradeStorageIfNeeded<T: LocalStorage>(storage: T, completion: (MigrationResult) -> Void) throws -> NSProgress? {
let fileURL = storage.fileURL
let metadata: [String: AnyObject] let metadata: [String: AnyObject]
do { do {
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType, storage.dynamicType.storeType,
URL: fileURL, URL: fileURL,
options: self.optionsForSQLiteStore() options: storage.storeOptions
) )
} }
catch { catch {
CoreStore.handleError( CoreStore.handleError(
error as NSError, error as NSError,
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata." "Failed to load \(typeName(storage)) metadata from URL \"\(fileURL)\"."
) )
throw error throw error
} }
return self.upgradeSQLiteStoreIfNeeded( return self.upgradeStorageIfNeeded(
fileURL: fileURL, storage,
metadata: metadata, metadata: metadata,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion completion: completion
) )
} }
/**
Checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support/<bundle id>" directory (or the "Caches/<bundle id>" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/
@warn_unused_result
public func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try requiredMigrationsForSQLiteStore(
fileURL: DataStack.DeprecatedDefaults.defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
mappingModelBundles: mappingModelBundles
)
}
/** /**
Checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version. Checks if the storage needs to be migrated to the `DataStack`'s managed object model version.
- parameter fileURL: the local file URL for the SQLite persistent store. - parameter fileURL: the local file URL for the SQLite persistent store.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
@@ -320,32 +270,34 @@ public extension DataStack {
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed. :return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/ */
@warn_unused_result @warn_unused_result
public func requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = DataStack.DeprecatedDefaults.defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] { public func requiredMigrationsForStorage<T: LocalStorage>(storage: T) throws -> [MigrationType] {
let fileURL = storage.fileURL
let metadata: [String : AnyObject] let metadata: [String : AnyObject]
do { do {
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType, storage.dynamicType.storeType,
URL: fileURL, URL: fileURL,
options: self.optionsForSQLiteStore() options: storage.storeOptions
) )
} }
catch { catch {
// TODO: throws when file not exists yet
CoreStore.handleError( CoreStore.handleError(
error as NSError, error as NSError,
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata." "Failed to load \(typeName(storage)) metadata from URL \"\(fileURL)\"."
) )
throw error throw error
} }
guard let migrationSteps = self.computeMigrationFromStoreMetadata(metadata, configuration: configuration, mappingModelBundles: mappingModelBundles) else { guard let migrationSteps = self.computeMigrationFromStorageMetadata(metadata, configuration: storage.configuration, mappingModelBundles: storage.mappingModelBundles) else {
let error = NSError(coreStoreErrorCode: .MappingModelNotFound) let error = NSError(coreStoreErrorCode: .MappingModelNotFound)
CoreStore.handleError( CoreStore.handleError(
error, error,
"Failed to find migration steps from the store at URL \"\(fileURL)\" to version model \"\(self.modelVersion)\"." "Failed to find migration steps from the \(typeName(storage)) at URL \"\(fileURL)\" to version model \"\(self.modelVersion)\"."
) )
throw error throw error
} }
@@ -356,13 +308,13 @@ public extension DataStack {
// MARK: Private // MARK: Private
private func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL, metadata: [String: AnyObject], configuration: String?, mappingModelBundles: [NSBundle]?, completion: (MigrationResult) -> Void) -> NSProgress? { private func upgradeStorageIfNeeded<T: LocalStorage>(storage: T, metadata: [String: AnyObject], completion: (MigrationResult) -> Void) -> NSProgress? {
guard let migrationSteps = self.computeMigrationFromStoreMetadata(metadata, configuration: configuration, mappingModelBundles: mappingModelBundles) else { guard let migrationSteps = self.computeMigrationFromStorageMetadata(metadata, configuration: storage.configuration, mappingModelBundles: storage.mappingModelBundles) else {
CoreStore.handleError( CoreStore.handleError(
NSError(coreStoreErrorCode: .MappingModelNotFound), NSError(coreStoreErrorCode: .MappingModelNotFound),
"Failed to find migration steps from the store at URL \"\(fileURL)\" to version model \"\(model)\"." "Failed to find migration steps from \(typeName(storage)) at URL \"\(storage.fileURL )\" to version model \"\(model)\"."
) )
GCDQueue.Main.async { GCDQueue.Main.async {
@@ -391,7 +343,6 @@ public extension DataStack {
let progress = NSProgress(parent: nil, userInfo: nil) let progress = NSProgress(parent: nil, userInfo: nil)
progress.totalUnitCount = numberOfMigrations progress.totalUnitCount = numberOfMigrations
// todo nsprogress crashing sometimes
for (sourceModel, destinationModel, mappingModel, _) in migrationSteps { for (sourceModel, destinationModel, mappingModel, _) in migrationSteps {
progress.becomeCurrentWithPendingUnitCount(1) progress.becomeCurrentWithPendingUnitCount(1)
@@ -411,8 +362,8 @@ public extension DataStack {
do { do {
try self.startMigrationForSQLiteStore( try self.startMigrationForStorage(
fileURL: fileURL, storage,
sourceModel: sourceModel, sourceModel: sourceModel,
destinationModel: destinationModel, destinationModel: destinationModel,
mappingModel: mappingModel, mappingModel: mappingModel,
@@ -456,7 +407,7 @@ public extension DataStack {
return progress return progress
} }
private func computeMigrationFromStoreMetadata(metadata: [String: AnyObject], configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil) -> [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]? { private func computeMigrationFromStorageMetadata(metadata: [String: AnyObject], configuration: String?, mappingModelBundles: [NSBundle]) -> [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]? {
let model = self.model let model = self.model
if model.isConfiguration(configuration, compatibleWithStoreMetadata: metadata) { if model.isConfiguration(configuration, compatibleWithStoreMetadata: metadata) {
@@ -534,27 +485,13 @@ public extension DataStack {
return nil return nil
} }
private func startMigrationForSQLiteStore(fileURL fileURL: NSURL, sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, progress: NSProgress) throws { private func startMigrationForStorage<T: LocalStorage>(storage: T, sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, progress: NSProgress) throws {
autoreleasepool { let fileURL = storage.fileURL
let journalUpdatingCoordinator = NSPersistentStoreCoordinator(managedObjectModel: sourceModel) let temporaryDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let store = try! journalUpdatingCoordinator.addPersistentStoreWithType( .URLByAppendingPathComponent(NSBundle.mainBundle().bundleIdentifier ?? "com.CoreStore.DataStack")
NSSQLiteStoreType, .URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString)
configuration: nil,
URL: fileURL,
options: [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
)
try! journalUpdatingCoordinator.removePersistentStore(store)
}
let migrationManager = MigrationManager(
sourceModel: sourceModel,
destinationModel: destinationModel,
progress: progress
)
let temporaryDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString)
let fileManager = NSFileManager.defaultManager() let fileManager = NSFileManager.defaultManager()
try! fileManager.createDirectoryAtURL( try! fileManager.createDirectoryAtURL(
@@ -563,17 +500,28 @@ public extension DataStack {
attributes: nil attributes: nil
) )
let temporaryFileURL = temporaryDirectoryURL.URLByAppendingPathComponent(fileURL.lastPathComponent!, isDirectory: false) let temporaryFileURL = temporaryDirectoryURL.URLByAppendingPathComponent(
fileURL.lastPathComponent!,
isDirectory: false
)
try storage.eraseStorageAndWait()
let migrationManager = MigrationManager(
sourceModel: sourceModel,
destinationModel: destinationModel,
progress: progress
)
do { do {
try migrationManager.migrateStoreFromURL( try migrationManager.migrateStoreFromURL(
fileURL, fileURL,
type: NSSQLiteStoreType, type: storage.dynamicType.storeType,
options: nil, options: nil,
withMappingModel: mappingModel, withMappingModel: mappingModel,
toDestinationURL: temporaryFileURL, toDestinationURL: temporaryFileURL,
destinationType: NSSQLiteStoreType, destinationType: storage.dynamicType.storeType,
destinationOptions: nil destinationOptions: nil
) )
} }
@@ -581,7 +529,7 @@ public extension DataStack {
do { do {
try fileManager.removeItemAtURL(temporaryDirectoryURL) try fileManager.removeItemAtURL(temporaryFileURL)
} }
catch _ { } catch _ { }
@@ -606,18 +554,12 @@ public extension DataStack {
) )
progress.completedUnitCount = progress.totalUnitCount progress.completedUnitCount = progress.totalUnitCount
do {
try fileManager.removeItemAtPath(fileURL.path! + "-shm")
}
catch _ { }
} }
catch { catch {
do { do {
try fileManager.removeItemAtURL(temporaryDirectoryURL) try fileManager.removeItemAtURL(temporaryFileURL)
} }
catch _ { } catch _ { }
@@ -631,4 +573,166 @@ public extension DataStack {
throw error throw error
} }
} }
// MARK: Deprecated
/**
Deprecated. Use `addStorage(_:completion:)` by passing a `InMemoryStore` instance.
*/
@available(*, deprecated=2.0.0, message="Use addStorage(_:completion:) by passing a InMemoryStore instance.")
public func addInMemoryStore(configuration configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
do {
try self.addStorage(
InMemoryStore(configuration: configuration),
completion: { result in
switch result {
case .Success(let storage):
completion(PersistentStoreResult(storage.internalStore!))
case .Failure(let error):
completion(PersistentStoreResult(error))
}
}
)
}
catch {
completion(PersistentStoreResult(error as NSError))
}
}
/**
Deprecated. Use `addStorage(_:completion:)` by passing a `LegacySQLiteStore` instance.
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`.
*/
@available(*, deprecated=2.0.0, message="Use addStorage(_:completion:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.")
public func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.addStorage(
LegacySQLiteStore(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles ?? NSBundle.allBundles(),
resetStoreOnModelMismatch: resetStoreOnModelMismatch
),
completion: { result in
switch result {
case .Success(let storage):
completion(PersistentStoreResult(storage.internalStore!))
case .Failure(let error):
completion(PersistentStoreResult(error))
}
}
)
}
/**
Deprecated. Use `addSQLiteStore(_:completion:)` by passing a `LegacySQLiteStore` instance.
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`.
*/
@available(*, deprecated=2.0.0, message="Use addSQLiteStore(_:completion:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.")
public func addSQLiteStore(fileURL fileURL: NSURL = LegacySQLiteStore.legacyDefaultFileURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.addStorage(
LegacySQLiteStore(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles ?? NSBundle.allBundles(),
resetStoreOnModelMismatch: resetStoreOnModelMismatch
),
completion: { result in
switch result {
case .Success(let storage):
completion(PersistentStoreResult(storage.internalStore!))
case .Failure(let error):
completion(PersistentStoreResult(error))
}
}
)
}
/**
Deprecated. Use `upgradeStorageIfNeeded(_:completion:)` by passing a `LegacySQLiteStore` instance.
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`.
*/
@available(*, deprecated=2.0.0, message="Use upgradeStorageIfNeeded(_:completion:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.")
public func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles(), completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.upgradeStorageIfNeeded(
LegacySQLiteStore(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles
),
completion: completion
)
}
/**
Deprecated. Use `upgradeStorageIfNeeded(_:completion:)` by passing a `LegacySQLiteStore` instance.
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`.
*/
@available(*, deprecated=2.0.0, message="Use upgradeStorageIfNeeded(_:completion:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.")
public func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = LegacySQLiteStore.legacyDefaultFileURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles(), completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.upgradeStorageIfNeeded(
LegacySQLiteStore(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles
),
completion: completion
)
}
/**
Deprecated. Use `requiredMigrationsForStorage(_:)` by passing a `LegacySQLiteStore` instance.
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`.
*/
@available(*, deprecated=2.0.0, message="Use requiredMigrationsForStorage(_:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.")
@warn_unused_result
public func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try self.requiredMigrationsForStorage(
LegacySQLiteStore(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles
)
)
}
/**
Deprecated. Use `requiredMigrationsForStorage(_:)` by passing a `LegacySQLiteStore` instance.
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`.
*/
@available(*, deprecated=2.0.0, message="Use requiredMigrationsForStorage(_:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.")
@warn_unused_result
public func requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = LegacySQLiteStore.legacyDefaultFileURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try self.requiredMigrationsForStorage(
LegacySQLiteStore(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles
)
)
}
} }

View File

@@ -72,12 +72,34 @@ public extension CoreStore {
/** /**
Adds a `StorageInterface` to the `defaultStack` and blocks until completion. Adds a `StorageInterface` to the `defaultStack` and blocks until completion.
- parameter store: the `StorageInterface` - parameter storage: the `StorageInterface`
- returns: the `StorageInterface` added to the `defaultStack` - returns: the `StorageInterface` added to the `defaultStack`
*/ */
public static func addStorageAndWait<T: StorageInterface>(store: T) throws -> T { public static func addStorageAndWait<T: StorageInterface>(storage: T) throws -> T {
return try self.defaultStack.addStorageAndWait(store) return try self.defaultStack.addStorageAndWait(storage)
}
/**
Creates a `LocalStorageface` of the specified store type with default values and adds it to the `defaultStack`. This method blocks until completion.
- parameter storeType: the `LocalStorageface` type
- returns: the local storage added to the stack
*/
public static func addStorageAndWait<T: LocalStorage where T: DefaultInitializableStore>(storageType: T.Type) throws -> T {
return try self.defaultStack.addStorageAndWait(storageType.init())
}
/**
Adds a `LocalStorage` to the stack and blocks until completion.
- parameter storage: the local storage
- returns: the local storage added to the stack
*/
public static func addStorageAndWait<T: LocalStorage>(storage: T) throws -> T {
return try self.defaultStack.addStorageAndWait(storage)
} }

View File

@@ -115,24 +115,20 @@ public final class DataStack {
/** /**
Adds a `StorageInterface` to the stack and blocks until completion. Adds a `StorageInterface` to the stack and blocks until completion.
- parameter store: the `StorageInterface` - parameter storage: the `StorageInterface`
- returns: the `StorageInterface` added to the stack - returns: the `StorageInterface` added to the stack
*/ */
public func addStorageAndWait<T: StorageInterface>(store: T) throws -> T { public func addStorageAndWait<T: StorageInterface>(storage: T) throws -> T {
CoreStore.assert( CoreStore.assert(
store.internalStore == nil, storage.internalStore == nil,
"The specified \"\(typeName(store))\" was already added to the data stack: \(store)" "The specified \"\(typeName(storage))\" was already added to the data stack: \(storage)"
)
CoreStore.assert(
T.validateStoreURL(store.storeURL),
"The specified store URL for the \"\(typeName(store))\" is invalid: \"\(store.storeURL)\""
) )
// TODO: check // TODO: check
// if let store = coordinator.persistentStoreForURL(fileURL) { // if let store = coordinator.persistentStoreForURL(fileURL) {
// //
// guard store.type == NSSQLiteStoreType // guard store.type == storage.dynamicType.storeType
// && store.configurationName == (configuration ?? Into.defaultConfigurationName) else { // && store.configurationName == (configuration ?? Into.defaultConfigurationName) else {
// //
// let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL) // let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
@@ -152,16 +148,114 @@ public final class DataStack {
do { do {
let persistentStore = try store.addToPersistentStoreCoordinatorSynchronously(self.coordinator) let persistentStore = try self.coordinator.addPersistentStoreSynchronously(
storage.dynamicType.storeType,
configuration: storage.configuration,
URL: nil,
options: storage.storeOptions
)
self.updateMetadataForPersistentStore(persistentStore) self.updateMetadataForPersistentStore(persistentStore)
store.internalStore = persistentStore storage.internalStore = persistentStore
return store return storage
} }
catch { catch {
CoreStore.handleError( CoreStore.handleError(
error as NSError, error as NSError,
"Failed to add \(typeName(T)) to the stack." "Failed to add \(typeName(storage)) to the stack."
)
throw error
}
}
/**
Creates a `LocalStorageface` of the specified store type with default values and adds it to the stack. This method blocks until completion.
- parameter storeType: the `LocalStorageface` type
- returns: the local storage added to the stack
*/
public func addStorageAndWait<T: LocalStorage where T: DefaultInitializableStore>(storageType: T.Type) throws -> T {
return try self.addStorageAndWait(storageType.init())
}
/**
Adds a `LocalStorage` to the stack and blocks until completion.
- parameter storage: the local storage
- returns: the local storage added to the stack
*/
public func addStorageAndWait<T: LocalStorage>(storage: T) throws -> T {
CoreStore.assert(
storage.internalStore == nil,
"The specified \"\(typeName(storage))\" was already added to the data stack: \(storage)"
)
let fileURL = storage.fileURL
CoreStore.assert(
fileURL.fileURL,
"The specified store URL for the \"\(typeName(storage))\" is invalid: \"\(fileURL)\""
)
if let persistentStore = coordinator.persistentStoreForURL(fileURL) {
guard persistentStore.type == storage.dynamicType.storeType
&& persistentStore.configurationName == (storage.configuration ?? Into.defaultConfigurationName) else {
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
CoreStore.handleError(
error,
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
)
throw error
}
storage.internalStore = persistentStore
return storage
}
do {
let coordinator = self.coordinator
let persistentStore = try coordinator.performBlockAndWait { () throws -> NSPersistentStore in
let fileManager = NSFileManager.defaultManager()
do {
try fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
return try coordinator.addPersistentStoreWithType(
storage.dynamicType.storeType,
configuration: storage.configuration,
URL: fileURL,
options: storage.storeOptions
)
}
catch let error as NSError where storage.resetStoreOnModelMismatch && error.isCoreDataMigrationError {
try storage.eraseStorageAndWait()
return try coordinator.addPersistentStoreWithType(
storage.dynamicType.storeType,
configuration: storage.configuration,
URL: fileURL,
options: storage.storeOptions
)
}
}
self.updateMetadataForPersistentStore(persistentStore)
storage.internalStore = persistentStore
return storage
}
catch {
CoreStore.handleError(
error as NSError,
"Failed to add \(typeName(storage)) to the stack."
) )
throw error throw error
} }
@@ -189,11 +283,6 @@ public final class DataStack {
return migrationQueue return migrationQueue
}() }()
internal func optionsForSQLiteStore() -> [String: AnyObject] {
return [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
}
internal func entityNameForEntityClass(entityClass: AnyClass) -> String? { internal func entityNameForEntityClass(entityClass: AnyClass) -> String? {
return self.model.entityNameForClass(entityClass) return self.model.entityNameForClass(entityClass)

View File

@@ -53,40 +53,8 @@ public class InMemoryStore: StorageInterface, DefaultInitializableStore {
return storeURL == nil return storeURL == nil
} }
public let storeURL: NSURL? = nil
public let configuration: String? public let configuration: String?
public let storeOptions: [String: AnyObject]? = nil public let storeOptions: [String: AnyObject]? = nil
public var internalStore: NSPersistentStore? public var internalStore: NSPersistentStore?
public func addToPersistentStoreCoordinatorSynchronously(coordinator: NSPersistentStoreCoordinator) throws -> NSPersistentStore {
return try coordinator.addPersistentStoreSynchronously(
self.dynamicType.storeType,
configuration: self.configuration,
URL: self.storeURL,
options: self.storeOptions
)
}
public func addToPersistentStoreCoordinatorAsynchronously(coordinator: NSPersistentStoreCoordinator, mappingModelBundles: [NSBundle]?, completion: (NSPersistentStore) -> Void, failure: (NSError) -> Void) throws {
coordinator.performBlock {
do {
let persistentStore = try coordinator.addPersistentStoreWithType(
self.dynamicType.storeType,
configuration: self.configuration,
URL: self.storeURL,
options: self.storeOptions
)
completion(persistentStore)
}
catch {
failure(error as NSError)
}
}
}
} }

View File

@@ -30,11 +30,12 @@ import Foundation
public final class LegacySQLiteStore: SQLiteStore { public final class LegacySQLiteStore: SQLiteStore {
public required init(fileURL: NSURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) { public required init(fileURL: NSURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false) {
super.init( super.init(
fileURL: fileURL, fileURL: fileURL,
configuration: configuration, configuration: configuration,
mappingModelBundles: mappingModelBundles,
resetStoreOnModelMismatch: resetStoreOnModelMismatch resetStoreOnModelMismatch: resetStoreOnModelMismatch
) )
} }
@@ -46,7 +47,7 @@ public final class LegacySQLiteStore: SQLiteStore {
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter resetStoreOnModelMismatch: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, a true value tells the `DataStack` to delete the store on model mismatch; a false value lets exceptions be thrown on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false. - parameter resetStoreOnModelMismatch: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, a true value tells the `DataStack` to delete the store on model mismatch; a false value lets exceptions be thrown on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
*/ */
public required init(fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) { public required init(fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false) {
super.init( super.init(
fileURL: LegacySQLiteStore.legacyDefaultRootDirectory.URLByAppendingPathComponent( fileURL: LegacySQLiteStore.legacyDefaultRootDirectory.URLByAppendingPathComponent(
@@ -54,6 +55,7 @@ public final class LegacySQLiteStore: SQLiteStore {
isDirectory: false isDirectory: false
), ),
configuration: configuration, configuration: configuration,
mappingModelBundles: mappingModelBundles,
resetStoreOnModelMismatch: resetStoreOnModelMismatch resetStoreOnModelMismatch: resetStoreOnModelMismatch
) )
} }
@@ -63,11 +65,7 @@ public final class LegacySQLiteStore: SQLiteStore {
public required init() { public required init() {
super.init( super.init(fileURL: LegacySQLiteStore.legacyDefaultFileURL)
fileURL: LegacySQLiteStore.legacyDefaultFileURL,
configuration: nil,
resetStoreOnModelMismatch: false
)
} }

View File

@@ -28,7 +28,7 @@ import CoreData
// MARK: - SQLiteStore // MARK: - SQLiteStore
public class SQLiteStore: StorageInterface, DefaultInitializableStore { public class SQLiteStore: LocalStorage, DefaultInitializableStore {
/** /**
Initializes an SQLite store interface from the given SQLite file URL. When this instance is passed to the `DataStack`'s `addStorage()` methods, a new SQLite file will be created if it does not exist. Initializes an SQLite store interface from the given SQLite file URL. When this instance is passed to the `DataStack`'s `addStorage()` methods, a new SQLite file will be created if it does not exist.
@@ -37,10 +37,11 @@ public class SQLiteStore: StorageInterface, DefaultInitializableStore {
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter resetStoreOnModelMismatch: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, a true value tells the `DataStack` to delete the store on model mismatch; a false value lets exceptions be thrown on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false. - parameter resetStoreOnModelMismatch: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, a true value tells the `DataStack` to delete the store on model mismatch; a false value lets exceptions be thrown on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
*/ */
public required init(fileURL: NSURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) { public required init(fileURL: NSURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false) {
self.fileURL = fileURL self.fileURL = fileURL
self.configuration = configuration self.configuration = configuration
self.mappingModelBundles = mappingModelBundles
self.resetStoreOnModelMismatch = resetStoreOnModelMismatch self.resetStoreOnModelMismatch = resetStoreOnModelMismatch
} }
@@ -51,11 +52,12 @@ public class SQLiteStore: StorageInterface, DefaultInitializableStore {
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter resetStoreOnModelMismatch: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, a true value tells the `DataStack` to delete the store on model mismatch; a false value lets exceptions be thrown on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false. - parameter resetStoreOnModelMismatch: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, a true value tells the `DataStack` to delete the store on model mismatch; a false value lets exceptions be thrown on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
*/ */
public required init(fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) { public required init(fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false) {
self.fileURL = SQLiteStore.defaultRootDirectory self.fileURL = SQLiteStore.defaultRootDirectory
.URLByAppendingPathComponent(fileName, isDirectory: false) .URLByAppendingPathComponent(fileName, isDirectory: false)
self.configuration = configuration self.configuration = configuration
self.mappingModelBundles = mappingModelBundles
self.resetStoreOnModelMismatch = resetStoreOnModelMismatch self.resetStoreOnModelMismatch = resetStoreOnModelMismatch
} }
@@ -66,10 +68,18 @@ public class SQLiteStore: StorageInterface, DefaultInitializableStore {
self.fileURL = SQLiteStore.defaultFileURL self.fileURL = SQLiteStore.defaultFileURL
self.configuration = nil self.configuration = nil
self.mappingModelBundles = NSBundle.allBundles()
self.resetStoreOnModelMismatch = false self.resetStoreOnModelMismatch = false
} }
// MAKR: LocalStorage
public let fileURL: NSURL
public let resetStoreOnModelMismatch: Bool
// MARK: StorageInterface // MARK: StorageInterface
public static let storeType = NSSQLiteStoreType public static let storeType = NSSQLiteStoreType
@@ -79,142 +89,28 @@ public class SQLiteStore: StorageInterface, DefaultInitializableStore {
return storeURL?.fileURL == true return storeURL?.fileURL == true
} }
public var storeURL: NSURL? {
return self.fileURL
}
public let configuration: String? public let configuration: String?
public let storeOptions: [String: AnyObject]? = [NSSQLitePragmasOption: ["journal_mode": "WAL"]] public let storeOptions: [String: AnyObject]? = [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
public let mappingModelBundles: [NSBundle]
public var internalStore: NSPersistentStore? public var internalStore: NSPersistentStore?
public func addToPersistentStoreCoordinatorSynchronously(coordinator: NSPersistentStoreCoordinator) throws -> NSPersistentStore { public func eraseStorageAndWait() throws {
let fileManager = NSFileManager.defaultManager() // TODO: check if attached to persistent store
do { let fileURL = self.fileURL
try autoreleasepool {
let fileURL = self.fileURL let journalUpdatingCoordinator = NSPersistentStoreCoordinator()
try fileManager.createDirectoryAtURL( let store = try journalUpdatingCoordinator.addPersistentStoreWithType(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
return try coordinator.addPersistentStoreSynchronously(
self.dynamicType.storeType, self.dynamicType.storeType,
configuration: self.configuration, configuration: self.configuration,
URL: fileURL, URL: fileURL,
options: self.storeOptions options: [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
) )
} try journalUpdatingCoordinator.removePersistentStore(store)
catch let error as NSError where resetStoreOnModelMismatch && error.isCoreDataMigrationError { try NSFileManager.defaultManager().removeItemAtURL(fileURL)
fileManager.removeSQLiteStoreAtURL(fileURL)
return try coordinator.addPersistentStoreSynchronously(
self.dynamicType.storeType,
configuration: self.configuration,
URL: fileURL,
options: self.storeOptions
)
}
}
public func addToPersistentStoreCoordinatorAsynchronously(coordinator: NSPersistentStoreCoordinator, mappingModelBundles: [NSBundle]?, completion: (NSPersistentStore) -> Void, failure: (NSError) -> Void) throws {
let fileManager = NSFileManager.defaultManager()
do {
let fileURL = self.fileURL
try fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
self.dynamicType.storeType,
URL: fileURL,
options: self.storeOptions
)
return self.upgradeSQLiteStoreIfNeeded(
fileURL: fileURL,
metadata: metadata,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: { (result) -> Void in
if case .Failure(let error) = result {
if resetStoreOnModelMismatch && error.isCoreDataMigrationError {
fileManager.removeSQLiteStoreAtURL(fileURL)
do {
let store = try self.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: false
)
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
}
catch {
completion(PersistentStoreResult(error as NSError))
}
return
}
completion(PersistentStoreResult(error))
return
}
do {
let store = try self.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: false
)
completion(PersistentStoreResult(store))
}
catch {
completion(PersistentStoreResult(error as NSError))
}
}
)
}
catch let error as NSError
where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain {
let store = try self.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: false
)
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
return nil
}
catch {
CoreStore.handleError(
error as NSError,
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
)
throw error
} }
} }
@@ -247,8 +143,4 @@ public class SQLiteStore: StorageInterface, DefaultInitializableStore {
.URLByAppendingPathComponent(applicationName, isDirectory: false) .URLByAppendingPathComponent(applicationName, isDirectory: false)
.URLByAppendingPathExtension("sqlite") .URLByAppendingPathExtension("sqlite")
}() }()
internal let fileURL: NSURL
internal let resetStoreOnModelMismatch: Bool
} }

View File

@@ -31,17 +31,11 @@ import CoreData
public protocol StorageInterface: class { public protocol StorageInterface: class {
static var storeType: String { get } static var storeType: String { get }
static func validateStoreURL(storeURL: NSURL?) -> Bool
var storeURL: NSURL? { get }
var configuration: String? { get } var configuration: String? { get }
var storeOptions: [String: AnyObject]? { get } var storeOptions: [String: AnyObject]? { get }
var internalStore: NSPersistentStore? { get set } var internalStore: NSPersistentStore? { get set }
func addToPersistentStoreCoordinatorSynchronously(coordinator: NSPersistentStoreCoordinator) throws -> NSPersistentStore
func addToPersistentStoreCoordinatorAsynchronously(coordinator: NSPersistentStoreCoordinator, mappingModelBundles: [NSBundle]?, completion: (NSPersistentStore) -> Void, failure: (NSError) -> Void) throws
} }
@@ -52,3 +46,14 @@ public protocol DefaultInitializableStore: StorageInterface {
init() init()
} }
// MARK: - LocalStorage
public protocol LocalStorage: StorageInterface {
var fileURL: NSURL { get }
var mappingModelBundles: [NSBundle] { get }
var resetStoreOnModelMismatch: Bool { get }
func eraseStorageAndWait() throws
}

View File

@@ -27,6 +27,50 @@ import Foundation
import CoreData import CoreData
// MARK: - SetupResult
public enum SetupResult<T: StorageInterface>: BooleanType {
case Success(T)
case Failure(NSError)
// MARK: BooleanType
public var boolValue: Bool {
switch self {
case .Success: return true
case .Failure: return false
}
}
// MARK: Internal
internal init(_ storage: T) {
self = .Success(storage)
}
internal init(_ error: NSError) {
self = .Failure(error)
}
internal init(_ errorCode: CoreStoreErrorCode) {
self.init(errorCode, userInfo: nil)
}
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo))
}
}
// MARK: - PersistentStoreResult // MARK: - PersistentStoreResult
/** /**

View File

@@ -52,8 +52,8 @@ class MigrationsDemoViewController: UIViewController {
let models = self.models let models = self.models
do { do {
let migrations = try dataStack.requiredMigrationsForSQLiteStore( let migrations = try dataStack.requiredMigrationsForStorage(
fileName: "MigrationDemo.sqlite" SQLiteStore(fileName: "MigrationDemo.sqlite")
) )
let storeVersion = migrations.first?.sourceVersion ?? dataStack.modelVersion let storeVersion = migrations.first?.sourceVersion ?? dataStack.modelVersion
@@ -158,8 +158,8 @@ class MigrationsDemoViewController: UIViewController {
) )
self.setEnabled(false) self.setEnabled(false)
let progress = try! dataStack.addSQLiteStore( let progress = try! dataStack.addStorage(
fileName: "MigrationDemo.sqlite", SQLiteStore(fileName: "MigrationDemo.sqlite"),
completion: { [weak self] (result) -> Void in completion: { [weak self] (result) -> Void in
guard let `self` = self else { guard let `self` = self else {