diff --git a/CoreStore.podspec b/CoreStore.podspec index 41aec0e..48aeb2f 100644 --- a/CoreStore.podspec +++ b/CoreStore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CoreStore" - s.version = "0.1.2" + s.version = "0.2.0" s.license = "MIT" s.summary = "Simple, elegant, and smart Core Data programming with Swift" s.homepage = "https://github.com/JohnEstropia/CoreStore" diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index c99c32d..2fe0aeb 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 2F03A54D19C5C872005002A5 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A54C19C5C872005002A5 /* CoreData.framework */; }; 2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; }; B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */; }; + B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */; }; B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */; }; B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; }; B5D372861A39CDDB00F583D9 /* TestEntity1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D372851A39CDDB00F583D9 /* TestEntity1.swift */; }; @@ -99,6 +100,7 @@ 2F03A54C19C5C872005002A5 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 2F291E2619C6D3CF007AF63F /* CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CoreStore.swift; sourceTree = ""; }; B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = ""; }; + B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Migration.swift"; sourceTree = ""; }; B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+CoreStore.swift"; sourceTree = ""; }; B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; B5D372851A39CDDB00F583D9 /* TestEntity1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity1.swift; sourceTree = ""; }; @@ -201,6 +203,7 @@ B5E84EE91AFF846E0064E85B /* Saving and Processing */, B5E84EFD1AFF847B0064E85B /* Fetching and Querying */, B5E84F191AFF84860064E85B /* Observing */, + B56964D11B22FF700075EE4A /* Migrating */, B5E84F261AFF84920064E85B /* Convenience Helpers */, B5E84F291AFF849C0064E85B /* Internal */, 2F03A53319C5C6DA005002A5 /* Supporting Files */, @@ -249,6 +252,14 @@ name = Frameworks; sourceTree = ""; }; + B56964D11B22FF700075EE4A /* Migrating */ = { + isa = PBXGroup; + children = ( + B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */, + ); + path = Migrating; + sourceTree = ""; + }; B5D806BB1A34715700A44484 /* Libraries */ = { isa = PBXGroup; children = ( @@ -531,6 +542,7 @@ B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */, B5E84F231AFF84860064E85B /* ManagedObjectListController.swift in Sources */, B5E84EF71AFF846E0064E85B /* DetachedDataTransaction.swift in Sources */, + B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */, B5E84EF51AFF846E0064E85B /* BaseDataTransaction.swift in Sources */, B5E84EFB1AFF846E0064E85B /* SaveResult.swift in Sources */, B5E84F0F1AFF847B0064E85B /* From.swift in Sources */, @@ -686,6 +698,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_WHOLE_MODULE_OPTIMIZATION = YES; }; name = Debug; }; @@ -709,6 +722,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_WHOLE_MODULE_OPTIMIZATION = YES; }; name = Release; }; diff --git a/CoreStore/Info.plist b/CoreStore/Info.plist index 6f05cb5..75994d8 100644 --- a/CoreStore/Info.plist +++ b/CoreStore/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.1.2 + 0.2.0 CFBundleSignature ???? CFBundleVersion diff --git a/CoreStore/Migrating/DataStack+Migration.swift b/CoreStore/Migrating/DataStack+Migration.swift new file mode 100644 index 0000000..871668b --- /dev/null +++ b/CoreStore/Migrating/DataStack+Migration.swift @@ -0,0 +1,357 @@ +// +// DataStack+Migration.swift +// CoreStore +// +// Copyright (c) 2015 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation +import CoreData +import GCDKit + + +// MARK: - DataStack + +public extension DataStack { + + // MARK: Public + + /** + Initializes a `DataStack` from the specified model name and a version-specific model name. + + :param: rootModelName the name of the (.xcdatamodeld) model file + :param: versionModelName the name of the version-specific (.xcdatamodeld) model file + */ + public convenience init(rootModelName: String, versionModelName: String) { + + let modelVersionURL: NSURL! = NSBundle.mainBundle().URLForResource( + rootModelName.stringByAppendingPathExtension("momd")!.stringByAppendingPathComponent(versionModelName), + withExtension: "mom" + ) + CoreStore.assert(modelVersionURL != nil, "Could not find a \"mom\" resource from the main bundle.") + + let managedObjectModel: NSManagedObjectModel! = NSManagedObjectModel(contentsOfURL: modelVersionURL) + CoreStore.assert(managedObjectModel != nil, "Could not create an <\(NSManagedObjectModel.self)> from the resource at URL \"\(modelVersionURL)\".") + + self.init(managedObjectModel: managedObjectModel) + } + + /** + Checks if the store at the specified filename and configuration needs to be migrated to the `DataStack`'s managed object model version. + + :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. + :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. + */ + public func needsMigrationForSQLiteStore(fileName: String, configuration: String? = nil) -> Bool? { + + return needsMigrationForSQLiteStore( + fileURL: applicationSupportDirectory.URLByAppendingPathComponent( + fileName, + isDirectory: false + ), + configuration: configuration + ) + } + + /** + Checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version. + + :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. + :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. + */ + public func needsMigrationForSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil) -> Bool? { + + var error: NSError? + let metadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( + NSSQLiteStoreType, + URL: fileURL, + error: &error + ) + if metadata == nil { + + CoreStore.handleError( + error ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\".") + return nil + } + + return !self.coordinator.managedObjectModel.isConfiguration( + configuration, + compatibleWithStoreMetadata: metadata + ) + } + + /** + EXPERIMENTAL + */ + private func upgradeSQLiteStoreIfNeeded(fileName: String, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) { + + self.upgradeSQLiteStoreIfNeeded( + fileURL: applicationSupportDirectory.URLByAppendingPathComponent( + fileName, + isDirectory: false + ), + configuration: configuration, + completion: completion + ) + } + + /** + EXPERIMENTAL + */ + private func upgradeSQLiteStoreIfNeeded(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) { + + var metadataError: NSError? + let metadata: [NSObject: AnyObject]! = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( + NSSQLiteStoreType, + URL: fileURL, + error: &metadataError + ) + if metadata == nil { + + CoreStore.handleError( + metadataError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\".") + + GCDQueue.Main.async { + + // TODO: inspect valid errors for metadataForPersistentStoreOfType() + completion(PersistentStoreResult(.PersistentStoreNotFound)) + } + return + } + + let coordinator = self.coordinator; + if let store = coordinator.persistentStoreForURL(fileURL) { + + let isExistingStoreAutomigrating = ((store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool) ?? false) + + if store.type == NSSQLiteStoreType + && isExistingStoreAutomigrating + && store.configurationName == (configuration ?? Into.defaultConfigurationName) { + + GCDQueue.Main.async { + + completion(PersistentStoreResult(store)) + } + return + } + + CoreStore.handleError( + NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL), + "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\" because a different <\(NSPersistentStore.self)> at that URL already exists.") + + GCDQueue.Main.async { + + completion(PersistentStoreResult(.DifferentPersistentStoreExistsAtURL)) + } + return + } + + let managedObjectModel = self.coordinator.managedObjectModel + let migrationManager = NSMigrationManager( + sourceModel: NSManagedObjectModel( + byMergingModels: [managedObjectModel], + forStoreMetadata: metadata! + )!, + destinationModel: managedObjectModel + ) + + var mappingModel: NSMappingModel! = NSMappingModel( + fromBundles: nil, // TODO: parametize + forSourceModel: migrationManager.sourceModel, + destinationModel: migrationManager.destinationModel + ) + var modelError: NSError? + if mappingModel == nil { + + mappingModel = NSMappingModel.inferredMappingModelForSourceModel( + migrationManager.sourceModel, + destinationModel: migrationManager.destinationModel, + error: &modelError + ) + } + if mappingModel == nil { + + CoreStore.handleError( + NSError(coreStoreErrorCode: .UnknownError), + "Failed to load an <\(NSMappingModel.self)> for migration from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\".") + + GCDQueue.Main.async { + + completion(PersistentStoreResult(.MappingModelNotFound)) + } + return + } + + let temporaryFileURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)!.URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString) + + var migrationError: NSError? + if !migrationManager.migrateStoreFromURL( + fileURL, + type: NSSQLiteStoreType, + options: nil, + withMappingModel: mappingModel, + toDestinationURL: temporaryFileURL, + destinationType: NSSQLiteStoreType, + destinationOptions: nil, + error: &migrationError + ) { + + CoreStore.handleError( + migrationError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed to prepare for migration from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\".") + + GCDQueue.Main.async { + + completion(PersistentStoreResult(.MigrationFailed)) + } + return + } + + } + + /** + 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`.) + + :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. 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. + :param: 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. + :param: 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 addSQLiteStore(fileName: String, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) { + + self.addSQLiteStore( + fileURL: applicationSupportDirectory.URLByAppendingPathComponent( + fileName, + isDirectory: false + ), + configuration: configuration, + completion: completion + ) + } + + /** + Asynchronously adds to the stack 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`.) + + :param: 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 ".sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. + :param: 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. + :param: 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 addSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) { + + var error: NSError? + let metadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( + NSSQLiteStoreType, + URL: fileURL, + error: &error + ) + if metadata == nil { + + CoreStore.handleError( + error ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\".") + + GCDQueue.Main.async { + + completion(PersistentStoreResult(.UnknownError)) + } + return + } + + let coordinator = self.coordinator; + if let store = coordinator.persistentStoreForURL(fileURL) { + + let isExistingStoreAutomigrating = ((store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool) ?? false) + + if store.type == NSSQLiteStoreType + && isExistingStoreAutomigrating + && store.configurationName == (configuration ?? Into.defaultConfigurationName) { + + GCDQueue.Main.async { + + completion(PersistentStoreResult(store)) + } + return + } + + CoreStore.handleError( + NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL), + "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\" because a different <\(NSPersistentStore.self)> at that URL already exists.") + + GCDQueue.Main.async { + + completion(PersistentStoreResult(.DifferentPersistentStoreExistsAtURL)) + } + return + } + + let fileManager = NSFileManager.defaultManager() + var directoryError: NSError? + if !fileManager.createDirectoryAtURL( + fileURL.URLByDeletingLastPathComponent!, + withIntermediateDirectories: true, + attributes: nil, + error: &directoryError) { + + CoreStore.handleError( + directoryError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed to create directory for SQLite store at \"\(fileURL)\".") + GCDQueue.Main.async { + + completion(PersistentStoreResult(directoryError!)) + } + return + } + + coordinator.performBlock { + + var persistentStoreError: NSError? + let store = coordinator.addPersistentStoreWithType( + NSSQLiteStoreType, + configuration: configuration, + URL: fileURL, + options: [NSSQLitePragmasOption: ["WAL": "journal_mode"], + NSInferMappingModelAutomaticallyOption: true, + NSMigratePersistentStoresAutomaticallyOption: true], + error: &persistentStoreError) + + if let store = store { + + GCDQueue.Main.async { + + self.updateMetadataForPersistentStore(store) + completion(PersistentStoreResult(store)) + } + } + else { + + GCDQueue.Main.async { + + CoreStore.handleError( + persistentStoreError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\".") + + completion(PersistentStoreResult(.UnknownError)) + } + } + } + } +} diff --git a/CoreStore/NSError+CoreStore.swift b/CoreStore/NSError+CoreStore.swift index cfa5fb2..20f6049 100644 --- a/CoreStore/NSError+CoreStore.swift +++ b/CoreStore/NSError+CoreStore.swift @@ -44,6 +44,21 @@ public enum CoreStoreErrorCode: Int { The `NSPersistentStore` could note be initialized because another store existed at the specified `NSURL`. */ case DifferentPersistentStoreExistsAtURL + + /** + The `NSPersistentStore` specified could not be found. + */ + case PersistentStoreNotFound + + /** + An `NSMappingModel` could not be found for a specific source and destination model versions. + */ + case MappingModelNotFound + + /** + An `NSMigrationManager` prepared to migrate the store. + */ + case MigrationFailed } diff --git a/CoreStore/Setting Up/CoreStore+Setup.swift b/CoreStore/Setting Up/CoreStore+Setup.swift index d6c5661..82db6dd 100644 --- a/CoreStore/Setting Up/CoreStore+Setup.swift +++ b/CoreStore/Setting Up/CoreStore+Setup.swift @@ -52,9 +52,9 @@ public extension CoreStore { :param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions 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 :returns: a `PersistentStoreResult` indicating success or failure. */ - public static func addSQLiteStore(fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { + public static func addSQLiteStoreAndWait(fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { - return self.defaultStack.addSQLiteStore( + return self.defaultStack.addSQLiteStoreAndWait( fileName, configuration: configuration, automigrating: automigrating, @@ -71,9 +71,9 @@ public extension CoreStore { :param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions 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. :returns: a `PersistentStoreResult` indicating success or failure. */ - public static func addSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { + public static func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { - return self.defaultStack.addSQLiteStore( + return self.defaultStack.addSQLiteStoreAndWait( fileURL: fileURL, configuration: configuration, automigrating: automigrating, diff --git a/CoreStore/Setting Up/DataStack.swift b/CoreStore/Setting Up/DataStack.swift index b4f89fc..18f25b8 100644 --- a/CoreStore/Setting Up/DataStack.swift +++ b/CoreStore/Setting Up/DataStack.swift @@ -28,9 +28,9 @@ import CoreData import GCDKit -private let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first as! NSURL +internal let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first as! NSURL -private let applicationName = ((NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData") +internal let applicationName = ((NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData") internal let defaultSQLiteStoreURL = applicationSupportDirectory.URLByAppendingPathComponent(applicationName, isDirectory: false).URLByAppendingPathExtension("sqlite") @@ -152,9 +152,9 @@ public final class DataStack { :param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions 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 :returns: a `PersistentStoreResult` indicating success or failure. */ - public func addSQLiteStore(fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { + public func addSQLiteStoreAndWait(fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { - return self.addSQLiteStore( + return self.addSQLiteStoreAndWait( fileURL: applicationSupportDirectory.URLByAppendingPathComponent( fileName, isDirectory: false @@ -174,7 +174,7 @@ public final class DataStack { :param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions 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. :returns: a `PersistentStoreResult` indicating success or failure. */ - public func addSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { + public func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { let coordinator = self.coordinator; if let store = coordinator.persistentStoreForURL(fileURL) { @@ -276,6 +276,7 @@ public final class DataStack { // MARK: Internal + internal let coordinator: NSPersistentStoreCoordinator internal let rootSavingContext: NSManagedObjectContext internal let mainContext: NSManagedObjectContext internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue") @@ -333,20 +334,7 @@ public final class DataStack { return returnValue } - - // MARK: Private - - private typealias EntityClassNameType = String - private typealias EntityNameType = String - private typealias ConfigurationNameType = String - - private let coordinator: NSPersistentStoreCoordinator - private let entityNameMapping: [EntityClassNameType: EntityNameType] - private let storeMetadataUpdateQueue = GCDQueue.createConcurrent("com.coreStore.persistentStoreBarrierQueue") - private var configurationStoreMapping = [ConfigurationNameType: NSPersistentStore]() - private var entityConfigurationsMapping = [EntityClassNameType: Set]() - - private func updateMetadataForPersistentStore(persistentStore: NSPersistentStore) { + internal func updateMetadataForPersistentStore(persistentStore: NSPersistentStore) { self.storeMetadataUpdateQueue.barrierAsync { @@ -358,4 +346,16 @@ public final class DataStack { } } } + + + // MARK: Private + + private typealias EntityClassNameType = String + private typealias EntityNameType = String + private typealias ConfigurationNameType = String + + private let entityNameMapping: [EntityClassNameType: EntityNameType] + private let storeMetadataUpdateQueue = GCDQueue.createConcurrent("com.coreStore.persistentStoreBarrierQueue") + private var configurationStoreMapping = [ConfigurationNameType: NSPersistentStore]() + private var entityConfigurationsMapping = [EntityClassNameType: Set]() } diff --git a/CoreStore/Setting Up/PersistentStoreResult.swift b/CoreStore/Setting Up/PersistentStoreResult.swift index 85c8466..a50f9fa 100644 --- a/CoreStore/Setting Up/PersistentStoreResult.swift +++ b/CoreStore/Setting Up/PersistentStoreResult.swift @@ -33,7 +33,7 @@ import CoreData The `PersistentStoreResult` indicates the result of initializing the persistent store. The `PersistentStoreResult` can be treated as a boolean: - let result = CoreStore.addSQLiteStore() + let result = CoreStore.addSQLiteStoreAndWait() if result { // succeeded } @@ -43,7 +43,7 @@ The `PersistentStoreResult` can be treated as a boolean: or as an `enum`, where the resulting associated object can also be inspected: - let result = CoreStore.addSQLiteStore() + let result = CoreStore.addSQLiteStoreAndWait() switch result { case .Success(let persistentStore): // persistentStore is the related NSPersistentStore instance diff --git a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj index 4139148..281bd17 100644 --- a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj +++ b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj @@ -24,12 +24,18 @@ B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */; }; B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3311B11DF3200F4F0C6 /* UserAccount.swift */; }; B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */; }; + B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */; }; + B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964D91B231BCA0075EE4A /* MaleAccount.swift */; }; + B56964DC1B231BCB0075EE4A /* FemaleAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964DB1B231BCB0075EE4A /* FemaleAccount.swift */; }; + B56964DF1B2321E30075EE4A /* MigrationDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */; }; + B569650C1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569650B1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift */; }; + B56965181B2E20CC0075EE4A /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965171B2E20CC0075EE4A /* TimeZone.swift */; }; + B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */; }; + B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */; }; B583A9201AF5F542001F76AF /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* CoreStore.framework */; }; B583A9211AF5F542001F76AF /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B5D9C9191B20AB1900E64F0E /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; }; B5D9C91A1B20AB1900E64F0E /* GCDKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - B5E7240F1B11F993006FB83F /* TwitterAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E7240E1B11F993006FB83F /* TwitterAccount.swift */; }; - B5E724111B11F994006FB83F /* FacebookAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E724101B11F994006FB83F /* FacebookAccount.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -98,10 +104,17 @@ B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackSetupDemoViewController.swift; sourceTree = ""; }; B566E3311B11DF3200F4F0C6 /* UserAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAccount.swift; sourceTree = ""; }; B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomLoggerViewController.swift; sourceTree = ""; }; + B56964D61B231AE90075EE4A /* StackSetupDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = StackSetupDemo.xcdatamodel; sourceTree = ""; }; + B56964D91B231BCA0075EE4A /* MaleAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MaleAccount.swift; sourceTree = ""; }; + B56964DB1B231BCB0075EE4A /* FemaleAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FemaleAccount.swift; sourceTree = ""; }; + B56964DE1B2321E30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = ""; }; + B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV2.xcdatamodel; sourceTree = ""; }; + B569650B1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingAndQueryingDemoViewController.swift; sourceTree = ""; }; + B56965171B2E20CC0075EE4A /* TimeZone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; + B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingResultsViewController.swift; sourceTree = ""; }; + B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryingResultsViewController.swift; sourceTree = ""; }; B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CoreStore.xcodeproj; path = ../CoreStore.xcodeproj; sourceTree = ""; }; B5D9C9181B20AB1900E64F0E /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GCDKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - B5E7240E1B11F993006FB83F /* TwitterAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwitterAccount.swift; sourceTree = ""; }; - B5E724101B11F994006FB83F /* FacebookAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FacebookAccount.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -175,11 +188,14 @@ B566E3271B117AE700F4F0C6 /* Stack Setup Demo */, B503FADA1AFDC71700F90881 /* List and Object Observers Demo */, B52977DB1B120F2C003D50A5 /* Transactions Demo */, + B56965091B2B35370075EE4A /* Fetching and Querying Demo */, B56964C61B20AC200075EE4A /* Loggers Demo */, B54AAD571AF4D26E00848AE0 /* Main.storyboard */, B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */, B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */, B54AAD501AF4D26E00848AE0 /* CoreStoreDemo.xcdatamodeld */, + B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */, + B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */, B54AAD4C1AF4D26E00848AE0 /* Supporting Files */, ); path = CoreStoreDemo; @@ -196,8 +212,8 @@ B566E3271B117AE700F4F0C6 /* Stack Setup Demo */ = { isa = PBXGroup; children = ( - B5E724101B11F994006FB83F /* FacebookAccount.swift */, - B5E7240E1B11F993006FB83F /* TwitterAccount.swift */, + B56964DB1B231BCB0075EE4A /* FemaleAccount.swift */, + B56964D91B231BCA0075EE4A /* MaleAccount.swift */, B566E3311B11DF3200F4F0C6 /* UserAccount.swift */, B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */, ); @@ -212,6 +228,17 @@ path = "Loggers Demo"; sourceTree = ""; }; + B56965091B2B35370075EE4A /* Fetching and Querying Demo */ = { + isa = PBXGroup; + children = ( + B56965171B2E20CC0075EE4A /* TimeZone.swift */, + B569650B1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift */, + B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */, + B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */, + ); + path = "Fetching and Querying Demo"; + sourceTree = ""; + }; B583A9151AF5F4F3001F76AF /* Products */ = { isa = PBXGroup; children = ( @@ -317,20 +344,26 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B56965181B2E20CC0075EE4A /* TimeZone.swift in Sources */, B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */, + B56964DF1B2321E30075EE4A /* MigrationDemo.xcdatamodeld in Sources */, B52977E41B121635003D50A5 /* Place.swift in Sources */, + B569650C1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift in Sources */, + B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */, B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */, B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */, B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */, B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */, - B5E724111B11F994006FB83F /* FacebookAccount.swift in Sources */, - B5E7240F1B11F993006FB83F /* TwitterAccount.swift in Sources */, + B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */, B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */, B54AAD521AF4D26E00848AE0 /* CoreStoreDemo.xcdatamodeld in Sources */, B503FAE11AFDC71700F90881 /* Palette.swift in Sources */, B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */, B503FADF1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift in Sources */, B54AAD4F1AF4D26E00848AE0 /* AppDelegate.swift in Sources */, + B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */, + B56964DC1B231BCB0075EE4A /* FemaleAccount.swift in Sources */, + B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -516,6 +549,27 @@ sourceTree = ""; versionGroupType = wrapper.xcdatamodel; }; + B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + B56964D61B231AE90075EE4A /* StackSetupDemo.xcdatamodel */, + ); + currentVersion = B56964D61B231AE90075EE4A /* StackSetupDemo.xcdatamodel */; + path = StackSetupDemo.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; + B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */, + B56964DE1B2321E30075EE4A /* MigrationDemo.xcdatamodel */, + ); + currentVersion = B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */; + path = MigrationDemo.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; /* End XCVersionGroup section */ }; rootObject = B54AAD411AF4D26E00848AE0 /* Project object */; diff --git a/CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata/johnestropia.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata/johnestropia.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..fe2b454 --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata/johnestropia.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,5 @@ + + + diff --git a/CoreStoreDemo/CoreStoreDemo/Base.lproj/Main.storyboard b/CoreStoreDemo/CoreStoreDemo/Base.lproj/Main.storyboard index d7ce7f7..8757afd 100644 --- a/CoreStoreDemo/CoreStoreDemo/Base.lproj/Main.storyboard +++ b/CoreStoreDemo/CoreStoreDemo/Base.lproj/Main.storyboard @@ -150,7 +150,7 @@ -