diff --git a/Sources/Internal/NSPersistentStoreCoordinator+Setup.swift b/Sources/Internal/NSPersistentStoreCoordinator+Setup.swift index 5046324..fe84725 100644 --- a/Sources/Internal/NSPersistentStoreCoordinator+Setup.swift +++ b/Sources/Internal/NSPersistentStoreCoordinator+Setup.swift @@ -60,24 +60,36 @@ internal extension NSPersistentStoreCoordinator { } @nonobjc - internal func performSynchronously(closure: () -> Void) { + internal func performSynchronously(closure: () -> T) -> T { + var result: T? #if USE_FRAMEWORKS - self.performBlockAndWait(closure) + self.performBlockAndWait { + + result = closure() + } #else if #available(iOS 8.0, *) { - self.performBlockAndWait(closure) + self.performBlockAndWait { + + result = closure() + } } else { self.lock() - autoreleasepool(closure) + autoreleasepool { + + result = closure() + } self.unlock() } #endif + + return result! } @nonobjc diff --git a/Sources/Migrating/CoreStore+Migration.swift b/Sources/Migrating/CoreStore+Migration.swift index 9c9675e..1136bd3 100644 --- a/Sources/Migrating/CoreStore+Migration.swift +++ b/Sources/Migrating/CoreStore+Migration.swift @@ -37,7 +37,7 @@ public extension CoreStore { /** Asynchronously adds a `StorageInterface` with default settings to the `defaultStack`. Migrations are also initiated by default. ``` - try CoreStore.addStorage( + CoreStore.addStorage( InMemoryStore.self, completion: { result in switch result { @@ -49,19 +49,17 @@ public extension CoreStore { ``` - parameter storeType: the storage type - - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` 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. Note that the `LocalStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration. - - throws: a `CoreStoreError` value indicating the failure - - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required + - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `StorageInterface` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration. */ - public static func addStorage(storeType: T.Type, completion: (SetupResult) -> Void) throws -> NSProgress? { + public static func addStorage(storeType: T.Type, completion: (SetupResult) -> Void) { - return try self.defaultStack.addStorage(storeType.init(), completion: completion) + self.defaultStack.addStorage(storeType.init(), completion: completion) } /** Asynchronously adds a `StorageInterface` to the `defaultStack`. Migrations are also initiated by default. ``` - try CoreStore.addStorage( + CoreStore.addStorage( InMemoryStore(configuration: "Config1"), completion: { result in switch result { @@ -73,19 +71,17 @@ public extension CoreStore { ``` - parameter storage: the storage - - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` 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. - - throws: a `CoreStoreError` value indicating the failure - - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required + - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `StorageInterface` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration. */ - public static func addStorage(storage: T, completion: (SetupResult) -> Void) throws -> NSProgress? { + public static func addStorage(storage: T, completion: (SetupResult) -> Void) { - return try self.defaultStack.addStorage(storage, completion: completion) + self.defaultStack.addStorage(storage, completion: completion) } /** Asynchronously adds a `LocalStorage` with default settings to the `defaultStack`. Migrations are also initiated by default. ``` - try CoreStore.addStorage( + let migrationProgress = CoreStore.addStorage( SQLiteStore.self, completion: { result in switch result { @@ -97,19 +93,18 @@ public extension CoreStore { ``` - parameter storeType: the local storage type - - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` 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. Note that the `LocalStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration. - - throws: a `CoreStoreError` value indicating the failure - - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required + - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `LocalStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration. + - returns: an `NSProgress` instance if a migration has started, or `nil` if either no migrations are required or if a failure occured. */ - public static func addStorage(storeType: T.Type, completion: (SetupResult) -> Void) throws -> NSProgress? { + public static func addStorage(storeType: T.Type, completion: (SetupResult) -> Void) -> NSProgress? { - return try self.defaultStack.addStorage(storeType.init(), completion: completion) + return self.defaultStack.addStorage(storeType.init(), completion: completion) } /** Asynchronously adds a `LocalStorage` to the `defaultStack`. Migrations are also initiated by default. ``` - try CoreStore.addStorage( + let migrationProgress = CoreStore.addStorage( SQLiteStore(fileName: "core_data.sqlite", configuration: "Config1"), completion: { result in switch result { @@ -121,13 +116,41 @@ public extension CoreStore { ``` - parameter storage: the local storage - - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` 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. Note that the `LocalStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration. - - throws: a `CoreStoreError` value indicating the failure - - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required + - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `LocalStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration. + - returns: an `NSProgress` instance if a migration has started, or `nil` if either no migrations are required or if a failure occured. */ - public static func addStorage(storage: T, completion: (SetupResult) -> Void) throws -> NSProgress? { + public static func addStorage(storage: T, completion: (SetupResult) -> Void) -> NSProgress? { - return try self.defaultStack.addStorage(storage, completion: completion) + return self.defaultStack.addStorage(storage, completion: completion) + } + + /** + Asynchronously adds a `CloudStorage` to the `defaultStack`. Migrations are also initiated by default. + ``` + let migrationProgress = dataStack.addStorage( + ICloudStore( + ubiquitousContentName: "MyAppCloudData", + ubiquitousContentTransactionLogsSubdirectory: "logs/config1", + ubiquitousContainerID: "iCloud.com.mycompany.myapp.containername", + ubiquitousPeerToken: "9614d658014f4151a95d8048fb717cf0", + configuration: "Config1", + cloudStorageOptions: .AllowSynchronousLightweightMigration + ), + completion: { result in + switch result { + case .Success(let storage): // ... + case .Failure(let error): // ... + } + } + ) + ``` + + - parameter storage: the cloud storage + - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `CloudStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `CloudStorage` was already added at the same URL and with the same configuration. + */ + public static func addStorage(storage: T, completion: (SetupResult) -> Void) { + + self.defaultStack.addStorage(storage, completion: completion) } /** diff --git a/Sources/Migrating/DataStack+Migration.swift b/Sources/Migrating/DataStack+Migration.swift index 31e7a9f..1f66c75 100644 --- a/Sources/Migrating/DataStack+Migration.swift +++ b/Sources/Migrating/DataStack+Migration.swift @@ -37,7 +37,7 @@ public extension DataStack { /** Asynchronously adds a `StorageInterface` with default settings to the stack. Migrations are also initiated by default. ``` - try dataStack.addStorage( + dataStack.addStorage( InMemoryStore.self, completion: { result in switch result { @@ -49,19 +49,17 @@ public extension DataStack { ``` - parameter storeType: the storage type - - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` 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. Note that the `LocalStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration. - - throws: a `CoreStoreError` value indicating the failure - - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required + - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `StorageInterface` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration. */ - public func addStorage(storeType: T.Type, completion: (SetupResult) -> Void) throws -> NSProgress? { + public func addStorage(storeType: T.Type, completion: (SetupResult) -> Void) { - return try self.addStorage(storeType.init(), completion: completion) + self.addStorage(storeType.init(), completion: completion) } /** Asynchronously adds a `StorageInterface` to the stack. Migrations are also initiated by default. ``` - try dataStack.addStorage( + dataStack.addStorage( InMemoryStore(configuration: "Config1"), completion: { result in switch result { @@ -73,11 +71,9 @@ public extension DataStack { ``` - parameter storage: the storage - - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` 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. - - throws: a `CoreStoreError` value indicating the failure - - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required + - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `StorageInterface` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration. */ - public func addStorage(storage: T, completion: (SetupResult) -> Void) throws -> NSProgress? { + public func addStorage(storage: T, completion: (SetupResult) -> Void) { self.coordinator.performAsynchronously { @@ -110,21 +106,18 @@ public extension DataStack { storeError, "Failed to add \(typeName(storage)) to the stack." ) - GCDQueue.Main.async { completion(SetupResult(storeError)) } } } - - return nil } /** Asynchronously adds a `LocalStorage` with default settings to the stack. Migrations are also initiated by default. ``` - try dataStack.addStorage( + let migrationProgress = dataStack.addStorage( SQLiteStore.self, completion: { result in switch result { @@ -136,19 +129,18 @@ public extension DataStack { ``` - parameter storeType: the local storage type - - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` 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. Note that the `LocalStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration. - - throws: a `CoreStoreError` value indicating the failure - - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required + - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `LocalStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration. + - returns: an `NSProgress` instance if a migration has started, or `nil` if either no migrations are required or if a failure occured. */ - public func addStorage(storeType: T.Type, completion: (SetupResult) -> Void) throws -> NSProgress? { + public func addStorage(storeType: T.Type, completion: (SetupResult) -> Void) -> NSProgress? { - return try self.addStorage(storeType.init(), completion: completion) + return self.addStorage(storeType.init(), completion: completion) } /** Asynchronously adds a `LocalStorage` to the stack. Migrations are also initiated by default. ``` - try dataStack.addStorage( + let migrationProgress = dataStack.addStorage( SQLiteStore(fileName: "core_data.sqlite", configuration: "Config1"), completion: { result in switch result { @@ -160,11 +152,10 @@ public extension DataStack { ``` - parameter storage: the local storage - - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` 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. Note that the `LocalStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration. - - throws: a `CoreStoreError` value indicating the failure - - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required + - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `LocalStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration. + - returns: an `NSProgress` instance if a migration has started, or `nil` if either no migrations are required or if a failure occured. */ - public func addStorage(storage: T, completion: (SetupResult) -> Void) throws -> NSProgress? { + public func addStorage(storage: T, completion: (SetupResult) -> Void) -> NSProgress? { let fileURL = storage.fileURL CoreStore.assert( @@ -172,7 +163,7 @@ public extension DataStack { "The specified URL for the \(typeName(storage)) is invalid: \"\(fileURL)\"" ) - return try self.coordinator.performSynchronously { + return self.coordinator.performSynchronously { if let _ = self.persistentStoreForStorage(storage) { @@ -200,7 +191,11 @@ public extension DataStack { error, "Failed to add \(typeName(storage)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists." ) - throw error + GCDQueue.Main.async { + + completion(SetupResult(error)) + } + return nil } do { @@ -263,11 +258,21 @@ public extension DataStack { catch let error as NSError where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain { - try self.addStorageAndWait(storage) - - GCDQueue.Main.async { + do { - completion(SetupResult(storage)) + try self.addStorageAndWait(storage) + + GCDQueue.Main.async { + + completion(SetupResult(storage)) + } + } + catch { + + GCDQueue.Main.async { + + completion(SetupResult(error)) + } } return nil } @@ -278,7 +283,11 @@ public extension DataStack { storeError, "Failed to load SQLite \(typeName(NSPersistentStore)) metadata." ) - throw storeError + GCDQueue.Main.async { + + completion(SetupResult(storeError)) + } + return nil } } } @@ -286,7 +295,7 @@ public extension DataStack { /** Asynchronously adds a `CloudStorage` to the stack. Migrations are also initiated by default. ``` - try dataStack.addStorage( + dataStack.addStorage( ICloudStore( ubiquitousContentName: "MyAppCloudData", ubiquitousContentTransactionLogsSubdirectory: "logs/config1", @@ -305,13 +314,12 @@ public extension DataStack { ``` - parameter storage: the cloud storage - - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` 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. Note that the `LocalStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration. - - throws: a `CoreStoreError` value indicating the failure + - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `CloudStorage` associated to the `SetupResult.Success` may not always be the same instance as the parameter argument if a previous `CloudStorage` was already added at the same URL and with the same configuration. */ - public func addStorage(storage: T, completion: (SetupResult) -> Void) throws { + public func addStorage(storage: T, completion: (SetupResult) -> Void) { let cacheFileURL = storage.cacheFileURL - try self.coordinator.performSynchronously { + self.coordinator.performSynchronously { if let _ = self.persistentStoreForStorage(storage) { @@ -339,7 +347,11 @@ public extension DataStack { error, "Failed to add \(typeName(storage)) at \"\(cacheFileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists." ) - throw error + GCDQueue.Main.async { + + completion(SetupResult(error)) + } + return } do { @@ -384,11 +396,21 @@ public extension DataStack { catch let error as NSError where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain { - try self.addStorageAndWait(storage) - - GCDQueue.Main.async { + do { - completion(SetupResult(storage)) + try self.addStorageAndWait(storage) + + GCDQueue.Main.async { + + completion(SetupResult(storage)) + } + } + catch { + + GCDQueue.Main.async { + + completion(SetupResult(error)) + } } } catch { @@ -398,7 +420,10 @@ public extension DataStack { storeError, "Failed to load \(typeName(NSPersistentStore)) metadata." ) - throw storeError + GCDQueue.Main.async { + + completion(SetupResult(storeError)) + } } } } @@ -407,7 +432,7 @@ public extension DataStack { Migrates a local storage to match the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack. - parameter storage: the local storage - - parameter completion: the closure to be executed on the main queue when the migration completes, either due to success or failure. The closure's `MigrationResult` 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 migration completes, either due to success or failure. The closure's `MigrationResult` argument indicates the result. - throws: a `CoreStoreError` value indicating the failure - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required */ diff --git a/Sources/Setup/StorageInterfaces/ICloudStore.swift b/Sources/Setup/StorageInterfaces/ICloudStore.swift index 77f79a6..57061fb 100644 --- a/Sources/Setup/StorageInterfaces/ICloudStore.swift +++ b/Sources/Setup/StorageInterfaces/ICloudStore.swift @@ -99,14 +99,19 @@ public class ICloudStore: CloudStorage { self.storeOptions = storeOptions } - public func addUbiquitousStoreObserver(observer: T) { + /** + Registers an `ICloudStoreObserver` to start receive notifications from the ubiquitous store + + - parameter observer: the observer to start sending ubiquitous notifications to + */ + public func addObserver(observer: T) { CoreStore.assert( NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread." ) - self.removeUbiquitousStoreObserver(observer) + self.removeObserver(observer) self.registerNotification( &self.willFinishInitialImportKey, @@ -182,7 +187,12 @@ public class ICloudStore: CloudStorage { ) } - public func removeUbiquitousStoreObserver(observer: ICloudStoreObserver) { + /** + Unregisters an `ICloudStoreObserver` to stop receiving notifications from the ubiquitous store + + - parameter observer: the observer to stop sending ubiquitous notifications to + */ + public func removeObserver(observer: ICloudStoreObserver) { CoreStore.assert( NSThread.isMainThread(), diff --git a/Sources/Setup/StorageInterfaces/ICloudStoreObserver.swift b/Sources/Setup/StorageInterfaces/ICloudStoreObserver.swift index 48ecfb3..ad56652 100644 --- a/Sources/Setup/StorageInterfaces/ICloudStoreObserver.swift +++ b/Sources/Setup/StorageInterfaces/ICloudStoreObserver.swift @@ -28,18 +28,77 @@ import Foundation // MARK: - ICloudStoreObserver +/** + Implement the `ICloudStoreObserver` protocol to observe ubiquitous storage notifications from the specified iCloud store. + Note that `ICloudStoreObserver` methods are only called when all the following conditions are true: + - the observer is registered to the `ICloudStore` via the `ICloudStore.addObserver(_:)` method + - the `ICloudStore` was added to a `DataStack` + - the `ICloudStore` and the `DataStack` are still persisted in memory + */ public protocol ICloudStoreObserver: class { + /** + Notifies that the initial ubiquitous store import will complete + + - parameter storage: the `ICloudStore` instance being observed + - parameter dataStack: the `DataStack` that manages the peristent store + */ func iCloudStoreWillFinishUbiquitousStoreInitialImport(storage storage: ICloudStore, dataStack: DataStack) + + /** + Notifies that the initial ubiquitous store import completed + + - parameter storage: the `ICloudStore` instance being observed + - parameter dataStack: the `DataStack` that manages the peristent store + */ func iCloudStoreDidFinishUbiquitousStoreInitialImport(storage storage: ICloudStore, dataStack: DataStack) + /** + Notifies that an iCloud account will be added to the coordinator + + - parameter storage: the `ICloudStore` instance being observed + - parameter dataStack: the `DataStack` that manages the peristent store + */ func iCloudStoreWillAddAccount(storage storage: ICloudStore, dataStack: DataStack) + + /** + Notifies that an iCloud account was added to the coordinator + + - parameter storage: the `ICloudStore` instance being observed + - parameter dataStack: the `DataStack` that manages the peristent store + */ func iCloudStoreDidAddAccount(storage storage: ICloudStore, dataStack: DataStack) + /** + Notifies that an iCloud account will be removed from the coordinator + + - parameter storage: the `ICloudStore` instance being observed + - parameter dataStack: the `DataStack` that manages the peristent store + */ func iCloudStoreWillRemoveAccount(storage storage: ICloudStore, dataStack: DataStack) + + /** + Notifies that an iCloud account was removed from the coordinator + + - parameter storage: the `ICloudStore` instance being observed + - parameter dataStack: the `DataStack` that manages the peristent store + */ func iCloudStoreDidRemoveAccount(storage storage: ICloudStore, dataStack: DataStack) + /** + Notifies that iCloud contents will be deleted + + - parameter storage: the `ICloudStore` instance being observed + - parameter dataStack: the `DataStack` that manages the peristent store + */ func iCloudStoreWillRemoveContent(storage storage: ICloudStore, dataStack: DataStack) + + /** + Notifies that iCloud contents were deleted + + - parameter storage: the `ICloudStore` instance being observed + - parameter dataStack: the `DataStack` that manages the peristent store + */ func iCloudStoreDidRemoveContent(storage storage: ICloudStore, dataStack: DataStack) }