created an asynchronous method for adding sqlite store to handle asynchronous migrations. note that this commit breaks previous usage of DataStack.addSQLiteStore(...), which is now renamed to addSQLiteStoreAndWait(...)

This commit is contained in:
John Rommel Estropia
2015-06-10 10:37:16 +09:00
parent c4171de86e
commit f18d29d9bc
22 changed files with 419 additions and 120 deletions

View File

@@ -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 = "<group>"; };
B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = "<group>"; };
B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Migration.swift"; sourceTree = "<group>"; };
B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+CoreStore.swift"; sourceTree = "<group>"; };
B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
B5D372851A39CDDB00F583D9 /* TestEntity1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity1.swift; sourceTree = "<group>"; };
@@ -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 = "<group>";
};
B56964D11B22FF700075EE4A /* Migrating */ = {
isa = PBXGroup;
children = (
B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */,
);
path = Migrating;
sourceTree = "<group>";
};
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 */,

View File

@@ -0,0 +1,227 @@
//
// 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
)
}
/**
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 "<Application name>.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))
}
}
}
}
}

View File

@@ -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,

View File

@@ -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<String>]()
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<String>]()
}

View File

@@ -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

View File

@@ -24,12 +24,14 @@
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 */; };
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 +100,13 @@
B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackSetupDemoViewController.swift; sourceTree = "<group>"; };
B566E3311B11DF3200F4F0C6 /* UserAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAccount.swift; sourceTree = "<group>"; };
B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomLoggerViewController.swift; sourceTree = "<group>"; };
B56964D61B231AE90075EE4A /* StackSetupDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = StackSetupDemo.xcdatamodel; sourceTree = "<group>"; };
B56964D91B231BCA0075EE4A /* MaleAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MaleAccount.swift; sourceTree = "<group>"; };
B56964DB1B231BCB0075EE4A /* FemaleAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FemaleAccount.swift; sourceTree = "<group>"; };
B56964DE1B2321E30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = "<group>"; };
B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV2.xcdatamodel; sourceTree = "<group>"; };
B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CoreStore.xcodeproj; path = ../CoreStore.xcodeproj; sourceTree = "<group>"; };
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 = "<group>"; };
B5E724101B11F994006FB83F /* FacebookAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FacebookAccount.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -180,6 +185,8 @@
B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */,
B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */,
B54AAD501AF4D26E00848AE0 /* CoreStoreDemo.xcdatamodeld */,
B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */,
B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */,
B54AAD4C1AF4D26E00848AE0 /* Supporting Files */,
);
path = CoreStoreDemo;
@@ -196,8 +203,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 */,
);
@@ -318,19 +325,21 @@
buildActionMask = 2147483647;
files = (
B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */,
B56964DF1B2321E30075EE4A /* MigrationDemo.xcdatamodeld in Sources */,
B52977E41B121635003D50A5 /* Place.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 */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -516,6 +525,27 @@
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
B56964D61B231AE90075EE4A /* StackSetupDemo.xcdatamodel */,
);
currentVersion = B56964D61B231AE90075EE4A /* StackSetupDemo.xcdatamodel */;
path = StackSetupDemo.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */,
B56964DE1B2321E30075EE4A /* MigrationDemo.xcdatamodel */,
);
currentVersion = B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */;
path = MigrationDemo.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = B54AAD411AF4D26E00848AE0 /* Project object */;

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
type = "1"
version = "2.0">
</Bucket>

View File

@@ -13,26 +13,14 @@
<attribute name="subtitle" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<entity name="UserAccount" representedClassName="CoreStoreDemo.UserAccount" syncable="YES">
<attribute name="accountType" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="friends" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<configuration name="ObservingDemo">
<memberEntity name="Palette"/>
</configuration>
<configuration name="SetupDemo_Jane">
<memberEntity name="UserAccount"/>
</configuration>
<configuration name="SetupDemo_John">
<memberEntity name="UserAccount"/>
</configuration>
<configuration name="TransactionsDemo">
<memberEntity name="Place"/>
</configuration>
<elements>
<element name="Palette" positionX="261" positionY="189" width="128" height="105"/>
<element name="Place" positionX="261" positionY="225" width="128" height="105"/>
<element name="UserAccount" positionX="261" positionY="216" width="128" height="90"/>
</elements>
</model>

View File

@@ -14,7 +14,7 @@ private struct Static {
static let palettes: ManagedObjectListController<Palette> = {
CoreStore.addSQLiteStore(
CoreStore.addSQLiteStoreAndWait(
"ColorsDemo.sqlite",
configuration: "ObservingDemo",
resetStoreOnMigrationFailure: true

View File

@@ -30,7 +30,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
super.viewDidLoad()
self.dataStack.addSQLiteStore("emptyStore.sqlite")
self.dataStack.addSQLiteStoreAndWait("emptyStore.sqlite")
CoreStore.logger = self
}
@@ -104,7 +104,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
}
case .Some(1):
self.dataStack.addSQLiteStore("emptyStore.sqlite", configuration: "invalidStore")
self.dataStack.addSQLiteStoreAndWait("emptyStore.sqlite", configuration: "invalidStore")
case .Some(2):
self.dataStack.beginAsynchronous { (transaction) -> Void in

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>MigrationDemoV2.xcdatamodel</string>
</dict>
</plist>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14D136" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="Organism" syncable="YES"/>
<elements>
<element name="Organism" positionX="-36" positionY="9" width="128" height="45"/>
</elements>
</model>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14D136" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="Organism" syncable="YES"/>
<elements>
<element name="Organism" positionX="-36" positionY="9" width="128" height="45"/>
</elements>
</model>

View File

@@ -1,15 +0,0 @@
//
// FacebookAccount.swift
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/05/24.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import Foundation
import CoreData
// MARK: - FacebookAccount
class FacebookAccount: TwitterAccount { }

View File

@@ -0,0 +1,15 @@
//
// FemaleAccount.swift
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/06/06.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import Foundation
import CoreData
class FemaleAccount: UserAccount {
}

View File

@@ -0,0 +1,15 @@
//
// MaleAccount.swift
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/06/06.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import Foundation
import CoreData
class MaleAccount: UserAccount {
}

View File

@@ -12,34 +12,33 @@ import CoreStore
private struct Static {
static let johnConfiguration = "SetupDemo_John"
static let janeConfiguration = "SetupDemo_Jane"
static let maleConfiguration = "MaleAccounts"
static let femaleConfiguration = "FemaleAccounts"
static let facebookStack: DataStack = {
let dataStack = DataStack(modelName: "CoreStoreDemo")
dataStack.addSQLiteStore(
"AccountsDemo_FB_John.sqlite",
configuration: johnConfiguration,
let dataStack = DataStack(modelName: "StackSetupDemo")
dataStack.addSQLiteStoreAndWait(
"AccountsDemo_FB_Male.sqlite",
configuration: maleConfiguration,
resetStoreOnMigrationFailure: true
)
dataStack.addSQLiteStore(
"AccountsDemo_FB_Jane.sqlite",
configuration: janeConfiguration,
dataStack.addSQLiteStoreAndWait(
"AccountsDemo_FB_Female.sqlite",
configuration: femaleConfiguration,
resetStoreOnMigrationFailure: true
)
dataStack.beginSynchronous { (transaction) -> Void in
transaction.deleteAll(From<UserAccount>(johnConfiguration))
transaction.deleteAll(From<UserAccount>(janeConfiguration))
transaction.deleteAll(From(UserAccount))
let account1 = transaction.create(Into<UserAccount>(johnConfiguration))
let account1 = transaction.create(Into<MaleAccount>(maleConfiguration))
account1.accountType = "Facebook"
account1.name = "John Smith HCD"
account1.friends = 42
let account2 = transaction.create(Into<UserAccount>(janeConfiguration))
let account2 = transaction.create(Into<FemaleAccount>(femaleConfiguration))
account2.accountType = "Facebook"
account2.name = "Jane Doe HCD"
account2.friends = 314
@@ -52,29 +51,28 @@ private struct Static {
static let twitterStack: DataStack = {
let dataStack = DataStack(modelName: "CoreStoreDemo")
dataStack.addSQLiteStore(
"AccountsDemo_TW_John.sqlite",
configuration: johnConfiguration,
let dataStack = DataStack(modelName: "StackSetupDemo")
dataStack.addSQLiteStoreAndWait(
"AccountsDemo_TW_Male.sqlite",
configuration: maleConfiguration,
resetStoreOnMigrationFailure: true
)
dataStack.addSQLiteStore(
"AccountsDemo_TW_Jane.sqlite",
configuration: janeConfiguration,
dataStack.addSQLiteStoreAndWait(
"AccountsDemo_TW_Female.sqlite",
configuration: femaleConfiguration,
resetStoreOnMigrationFailure: true
)
dataStack.beginSynchronous { (transaction) -> Void in
transaction.deleteAll(From<UserAccount>(johnConfiguration))
transaction.deleteAll(From<UserAccount>(janeConfiguration))
transaction.deleteAll(From(UserAccount))
let account1 = transaction.create(Into<UserAccount>(johnConfiguration))
let account1 = transaction.create(Into<MaleAccount>(maleConfiguration))
account1.accountType = "Twitter"
account1.name = "#johnsmith_hcd"
account1.friends = 7
let account2 = transaction.create(Into<UserAccount>(janeConfiguration))
let account2 = transaction.create(Into<FemaleAccount>(femaleConfiguration))
account2.accountType = "Twitter"
account2.name = "#janedoe_hcd"
account2.friends = 100
@@ -94,14 +92,8 @@ private struct Static {
class StackSetupDemoViewController: UITableViewController {
let accounts = [
[
Static.facebookStack.fetchOne(From<UserAccount>(Static.johnConfiguration))!,
Static.facebookStack.fetchOne(From<UserAccount>(Static.janeConfiguration))!
],
[
Static.twitterStack.fetchOne(From<UserAccount>(Static.johnConfiguration))!,
Static.twitterStack.fetchOne(From<UserAccount>(Static.janeConfiguration))!
]
Static.facebookStack.fetchAll(From(UserAccount)) ?? [],
Static.twitterStack.fetchAll(From(UserAccount)) ?? []
]
@@ -169,11 +161,11 @@ class StackSetupDemoViewController: UITableViewController {
switch section {
case 0:
let count = Static.facebookStack.fetchCount(From(UserAccount)) ?? 0
let count = self.accounts[section].count
return "Facebook Accounts (\(count) users)"
case 1:
let count = Static.twitterStack.fetchCount(From(UserAccount)) ?? 0
let count = self.accounts[section].count
return "Twitter Accounts (\(count) users)"
default:

View File

@@ -1,15 +0,0 @@
//
// TwitterAccount.swift
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/05/24.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import Foundation
import CoreData
// MARK: - TwitterAccount
class TwitterAccount: UserAccount { }

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14D136" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="FemaleAccount" representedClassName="CoreStoreDemo.FemaleAccount" parentEntity="UserAccount" syncable="YES"/>
<entity name="MaleAccount" representedClassName="CoreStoreDemo.MaleAccount" parentEntity="UserAccount" syncable="YES"/>
<entity name="UserAccount" representedClassName="CoreStoreDemo.UserAccount" isAbstract="YES" syncable="YES">
<attribute name="accountType" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="friends" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<configuration name="FemaleAccounts">
<memberEntity name="FemaleAccount"/>
<memberEntity name="UserAccount"/>
</configuration>
<configuration name="MaleAccounts">
<memberEntity name="MaleAccount"/>
<memberEntity name="UserAccount"/>
</configuration>
<elements>
<element name="UserAccount" positionX="-63" positionY="-18" width="128" height="90"/>
<element name="MaleAccount" positionX="-54" positionY="18" width="128" height="45"/>
<element name="FemaleAccount" positionX="-36" positionY="27" width="128" height="45"/>
</elements>
</model>

View File

@@ -18,7 +18,7 @@ private struct Static {
static let placeController: ManagedObjectController<Place> = {
CoreStore.addSQLiteStore(
CoreStore.addSQLiteStoreAndWait(
"PlaceDemo.sqlite",
configuration: "TransactionsDemo",
resetStoreOnMigrationFailure: true

View File

@@ -47,7 +47,7 @@ class CoreStoreTests: XCTestCase {
CoreStore.defaultStack = stack
XCTAssert(CoreStore.defaultStack === stack, "CoreStore.defaultStack === stack")
switch stack.addSQLiteStore("ConfigStore1.sqlite", configuration: "Config1", resetStoreOnMigrationFailure: true){
switch stack.addSQLiteStoreAndWait("ConfigStore1.sqlite", configuration: "Config1", resetStoreOnMigrationFailure: true){
case .Failure(let error):
XCTFail(error.description)
@@ -56,7 +56,7 @@ class CoreStoreTests: XCTestCase {
break
}
switch stack.addSQLiteStore("ConfigStore2.sqlite", configuration: "Config2", resetStoreOnMigrationFailure: true){
switch stack.addSQLiteStoreAndWait("ConfigStore2.sqlite", configuration: "Config2", resetStoreOnMigrationFailure: true){
case .Failure(let error):
XCTFail(error.description)

View File

@@ -25,7 +25,7 @@ Simple, elegant, and smart Core Data programming with Swift
Quick-setup:
```swift
CoreStore.addSQLiteStore("MyStore.sqlite")
CoreStore.addSQLiteStoreAndWait("MyStore.sqlite")
```
Simple transactions:
@@ -107,7 +107,7 @@ This allows for a butter-smooth main thread, while still taking advantage of saf
## <a id="setup"></a>Setting up
The simplest way to initialize CoreStore is to add a default store to the default stack:
```swift
CoreStore.addSQLiteStore()
CoreStore.addSQLiteStoreAndWait()
```
This one-liner does the following:
- Triggers the lazy-initialization of `CoreStore.defaultStack` with a default `DataStack`
@@ -126,7 +126,7 @@ case .Failure(let error): // error is an NSError instance
println("Failed creating an in-memory store with error: \(error.description)"
}
switch dataStack.addSQLiteStore(
switch dataStack.addSQLiteStoreAndWait(
fileURL: sqliteFileURL, // set the target file URL for the sqlite file
configuration: "Config2", // use entities from the "Config2" configuration in the .xcdatamodeld file
automigrating: true, // automatically run lightweight migrations or entity policy migrations when needed
@@ -146,7 +146,7 @@ class MyViewController: UIViewController {
let dataStack = DataStack(modelName: "MyModel")
override func viewDidLoad() {
super.viewDidLoad()
self.dataStack.addSQLiteStore()
self.dataStack.addSQLiteStoreAndWait()
}
func methodToBeCalledLaterOn() {
let objects = self.dataStack.fetchAll(From(MyEntity))
@@ -159,7 +159,7 @@ The difference is when you set the stack as the `CoreStore.defaultStack`, you ca
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
CoreStore.addSQLiteStore()
CoreStore.addSQLiteStoreAndWait()
}
func methodToBeCalledLaterOn() {
let objects = CoreStore.fetchAll(From(MyEntity))