diff --git a/HardcoreData.xcodeproj/project.pbxproj b/HardcoreData.xcodeproj/project.pbxproj index 15d8d4c..4dd7175 100644 --- a/HardcoreData.xcodeproj/project.pbxproj +++ b/HardcoreData.xcodeproj/project.pbxproj @@ -665,6 +665,7 @@ ); INFOPLIST_FILE = HardcoreData/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_SWIFT_FLAGS = "-D DEBUG"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -688,6 +689,7 @@ ); INFOPLIST_FILE = HardcoreData/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/HardcoreData/Fetching and Querying/Concrete Clauses/From.swift b/HardcoreData/Fetching and Querying/Concrete Clauses/From.swift index bec634a..6c6da55 100644 --- a/HardcoreData/Fetching and Querying/Concrete Clauses/From.swift +++ b/HardcoreData/Fetching and Querying/Concrete Clauses/From.swift @@ -34,6 +34,125 @@ A `Form` clause binds the `NSManagedObject` entity type to the generics type sys */ public struct From { - public init(){ } - public init(_ entity: T.Type) { } + // MARK: Public + + public init(){ + + self.findPersistentStores = { _ in nil } + } + + public init(_ entity: T.Type) { + + self.findPersistentStores = { _ in nil } + } + + public init(_ configurations: String...) { + + self.init(configurations: configurations) + } + + public init(_ configurations: [String]) { + + self.init(configurations: configurations) + } + + public init(_ entity: T.Type, _ configurations: String...) { + + self.init(configurations: configurations) + } + + public init(_ entity: T.Type, _ configurations: [String]) { + + self.init(configurations: configurations) + } + + public init(_ storeURLs: NSURL...) { + + self.init(storeURLs: storeURLs) + } + + public init(_ storeURLs: [NSURL]) { + + self.init(storeURLs: storeURLs) + } + + public init(_ entity: T.Type, _ storeURLs: NSURL...) { + + self.init(storeURLs: storeURLs) + } + + public init(_ entity: T.Type, _ storeURLs: [NSURL]) { + + self.init(storeURLs: storeURLs) + } + + public init(_ persistentStores: NSPersistentStore...) { + + self.init(persistentStores: persistentStores) + } + + public init(_ persistentStores: [NSPersistentStore]) { + + self.init(persistentStores: persistentStores) + } + + public init(_ entity: T.Type, _ persistentStores: NSPersistentStore...) { + + self.init(persistentStores: persistentStores) + } + + public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) { + + self.init(persistentStores: persistentStores) + } + + + // MARK: Internal + + internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) { + + fetchRequest.entity = context.entityDescriptionForEntityClass(T.self) + fetchRequest.affectedStores = self.findPersistentStores(context: context) + } + + + // MARK: Private + + private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]? + + private init(configurations: [String]) { + + let configurationsSet = Set(configurations) + self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in + + return context.parentStack?.persistentStoresForEntityClass(T.self)?.filter { + + return configurationsSet.contains($0.configurationName) + } + } + } + + private init(storeURLs: [NSURL]) { + + let storeURLsSet = Set(storeURLs) + self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in + + return context.parentStack?.persistentStoresForEntityClass(T.self)?.filter { + + return $0.URL != nil && storeURLsSet.contains($0.URL!) + } + } + } + + private init(persistentStores: [NSPersistentStore]) { + + let persistentStores = Set(persistentStores) + self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in + + return context.parentStack?.persistentStoresForEntityClass(T.self)?.filter { + + return persistentStores.contains($0) + } + } + } } diff --git a/HardcoreData/Fetching and Querying/DataStack+Querying.swift b/HardcoreData/Fetching and Querying/DataStack+Querying.swift index 224401c..a8fc8b8 100644 --- a/HardcoreData/Fetching and Querying/DataStack+Querying.swift +++ b/HardcoreData/Fetching and Querying/DataStack+Querying.swift @@ -43,7 +43,7 @@ public extension DataStack { */ public func fetchOne(from: From, _ fetchClauses: FetchClause...) -> T? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") return self.mainContext.fetchOne(from, fetchClauses) } @@ -57,7 +57,7 @@ public extension DataStack { */ public func fetchOne(from: From, _ fetchClauses: [FetchClause]) -> T? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") return self.mainContext.fetchOne(from, fetchClauses) } @@ -71,7 +71,7 @@ public extension DataStack { */ public func fetchAll(from: From, _ fetchClauses: FetchClause...) -> [T]? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") return self.mainContext.fetchAll(from, fetchClauses) } @@ -85,7 +85,7 @@ public extension DataStack { */ public func fetchAll(from: From, _ fetchClauses: [FetchClause]) -> [T]? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") return self.mainContext.fetchAll(from, fetchClauses) } @@ -99,7 +99,7 @@ public extension DataStack { */ public func fetchCount(from: From, _ fetchClauses: FetchClause...) -> Int? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") return self.mainContext.fetchCount(from, fetchClauses) } @@ -113,7 +113,7 @@ public extension DataStack { */ public func fetchCount(from: From, _ fetchClauses: [FetchClause]) -> Int? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") return self.mainContext.fetchCount(from, fetchClauses) } @@ -127,7 +127,7 @@ public extension DataStack { */ public func fetchObjectID(from: From, _ fetchClauses: FetchClause...) -> NSManagedObjectID? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") return self.mainContext.fetchObjectID(from, fetchClauses) } @@ -141,7 +141,7 @@ public extension DataStack { */ public func fetchObjectID(from: From, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") return self.mainContext.fetchObjectID(from, fetchClauses) } @@ -155,7 +155,7 @@ public extension DataStack { */ public func fetchObjectIDs(from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") return self.mainContext.fetchObjectIDs(from, fetchClauses) } @@ -169,39 +169,11 @@ public extension DataStack { */ public func fetchObjectIDs(from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") return self.mainContext.fetchObjectIDs(from, fetchClauses) } - /** - Deletes all `NSManagedObject`'s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - :param: from a `From` clause indicating the entity type - :param: deleteClauses a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number of `NSManagedObject`'s deleted - */ - public func deleteAll(from: From, _ deleteClauses: DeleteClause...) -> Int? { - - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to delete from a \(typeName(self)) outside the main queue.") - - return self.mainContext.deleteAll(from, deleteClauses) - } - - /** - Deletes all `NSManagedObject`'s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - :param: from a `From` clause indicating the entity type - :param: deleteClauses a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number of `NSManagedObject`'s deleted - */ - public func deleteAll(from: From, _ deleteClauses: [DeleteClause]) -> Int? { - - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to delete from a \(typeName(self)) outside the main queue.") - - return self.mainContext.deleteAll(from, deleteClauses) - } - /** Queries aggregate values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. @@ -214,7 +186,7 @@ public extension DataStack { */ public func queryValue(from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> U? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to query from a \(typeName(self)) outside the main thread.") return self.mainContext.queryValue(from, selectClause, queryClauses) } @@ -231,7 +203,7 @@ public extension DataStack { */ public func queryValue(from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> U? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to query from a \(typeName(self)) outside the main thread.") return self.mainContext.queryValue(from, selectClause, queryClauses) } @@ -248,7 +220,7 @@ public extension DataStack { */ public func queryAttributes(from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to query from a \(typeName(self)) outside the main thread.") return self.mainContext.queryAttributes(from, selectClause, queryClauses) } @@ -265,7 +237,7 @@ public extension DataStack { */ public func queryAttributes(from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to query from a \(typeName(self)) outside the main thread.") return self.mainContext.queryAttributes(from, selectClause, queryClauses) } diff --git a/HardcoreData/Fetching and Querying/HardcoreData+Querying.swift b/HardcoreData/Fetching and Querying/HardcoreData+Querying.swift index a625924..56891b1 100644 --- a/HardcoreData/Fetching and Querying/HardcoreData+Querying.swift +++ b/HardcoreData/Fetching and Querying/HardcoreData+Querying.swift @@ -151,30 +151,6 @@ public extension HardcoreData { return self.defaultStack.fetchObjectIDs(from, fetchClauses) } - /** - Using the `defaultStack`, deletes all `NSManagedObject`'s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - :param: from a `From` clause indicating the entity type - :param: deleteClauses a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number of `NSManagedObject`'s deleted - */ - public static func deleteAll(from: From, _ deleteClauses: DeleteClause...) -> Int? { - - return self.defaultStack.deleteAll(from, deleteClauses) - } - - /** - Deletes all `NSManagedObject`'s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - :param: from a `From` clause indicating the entity type - :param: deleteClauses a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number of `NSManagedObject`'s deleted - */ - public static func deleteAll(from: From, _ deleteClauses: [DeleteClause]) -> Int? { - - return self.defaultStack.deleteAll(from, deleteClauses) - } - /** Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. diff --git a/HardcoreData/Internal/NSManagedObjectContext+Querying.swift b/HardcoreData/Internal/NSManagedObjectContext+Querying.swift index 73f4aa4..75dcce0 100644 --- a/HardcoreData/Internal/NSManagedObjectContext+Querying.swift +++ b/HardcoreData/Internal/NSManagedObjectContext+Querying.swift @@ -41,7 +41,8 @@ internal extension NSManagedObjectContext { internal func fetchOne(from: From, _ fetchClauses: [FetchClause]) -> T? { let fetchRequest = NSFetchRequest() - fetchRequest.entity = self.entityDescriptionForEntityClass(T) + from.applyToFetchRequest(fetchRequest, context: self) + fetchRequest.fetchLimit = 1 fetchRequest.resultType = .ManagedObjectResultType @@ -75,7 +76,8 @@ internal extension NSManagedObjectContext { internal func fetchAll(from: From, _ fetchClauses: [FetchClause]) -> [T]? { let fetchRequest = NSFetchRequest() - fetchRequest.entity = self.entityDescriptionForEntityClass(T) + from.applyToFetchRequest(fetchRequest, context: self) + fetchRequest.fetchLimit = 0 fetchRequest.resultType = .ManagedObjectResultType @@ -109,7 +111,7 @@ internal extension NSManagedObjectContext { internal func fetchCount(from: From, _ fetchClauses: [FetchClause]) -> Int? { let fetchRequest = NSFetchRequest() - fetchRequest.entity = self.entityDescriptionForEntityClass(T) + from.applyToFetchRequest(fetchRequest, context: self) for clause in fetchClauses { @@ -141,7 +143,8 @@ internal extension NSManagedObjectContext { internal func fetchObjectID(from: From, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { let fetchRequest = NSFetchRequest() - fetchRequest.entity = self.entityDescriptionForEntityClass(T) + from.applyToFetchRequest(fetchRequest, context: self) + fetchRequest.fetchLimit = 1 fetchRequest.resultType = .ManagedObjectIDResultType @@ -175,7 +178,8 @@ internal extension NSManagedObjectContext { internal func fetchObjectIDs(from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { let fetchRequest = NSFetchRequest() - fetchRequest.entity = self.entityDescriptionForEntityClass(T) + from.applyToFetchRequest(fetchRequest, context: self) + fetchRequest.fetchLimit = 0 fetchRequest.resultType = .ManagedObjectIDResultType @@ -209,7 +213,8 @@ internal extension NSManagedObjectContext { internal func deleteAll(from: From, _ deleteClauses: [DeleteClause]) -> Int? { let fetchRequest = NSFetchRequest() - fetchRequest.entity = self.entityDescriptionForEntityClass(T) + from.applyToFetchRequest(fetchRequest, context: self) + fetchRequest.fetchLimit = 0 fetchRequest.resultType = .ManagedObjectResultType fetchRequest.returnsObjectsAsFaults = true @@ -254,7 +259,8 @@ internal extension NSManagedObjectContext { internal func queryValue(from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> U? { let fetchRequest = NSFetchRequest() - fetchRequest.entity = self.entityDescriptionForEntityClass(T) + from.applyToFetchRequest(fetchRequest, context: self) + fetchRequest.fetchLimit = 0 selectClause.applyToFetchRequest(fetchRequest) @@ -294,7 +300,8 @@ internal extension NSManagedObjectContext { internal func queryAttributes(from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { let fetchRequest = NSFetchRequest() - fetchRequest.entity = self.entityDescriptionForEntityClass(T) + from.applyToFetchRequest(fetchRequest, context: self) + fetchRequest.fetchLimit = 0 selectClause.applyToFetchRequest(fetchRequest) diff --git a/HardcoreData/Observing/DataStack+Observing.swift b/HardcoreData/Observing/DataStack+Observing.swift index a7022a6..f93d55e 100644 --- a/HardcoreData/Observing/DataStack+Observing.swift +++ b/HardcoreData/Observing/DataStack+Observing.swift @@ -42,7 +42,7 @@ public extension DataStack { */ public func observeObject(object: T) -> ManagedObjectController { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to observe objects from \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to observe objects from \(typeName(self)) outside the main thread.") return ManagedObjectController( dataStack: self, @@ -71,11 +71,11 @@ public extension DataStack { */ public func observeObjectList(from: From, _ fetchClauses: [FetchClause]) -> ManagedObjectListController { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to observe objects from \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to observe objects from \(typeName(self)) outside the main thread.") return ManagedObjectListController( dataStack: self, - entity: T.self, + from: from, sectionedBy: nil, fetchClauses: fetchClauses ) @@ -104,11 +104,11 @@ public extension DataStack { */ public func observeSectionedList(from: From, _ sectionedBy: SectionedBy, _ fetchClauses: [FetchClause]) -> ManagedObjectListController { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to observe objects from \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to observe objects from \(typeName(self)) outside the main thread.") return ManagedObjectListController( dataStack: self, - entity: T.self, + from: from, sectionedBy: sectionedBy, fetchClauses: fetchClauses ) diff --git a/HardcoreData/Observing/ManagedObjectController.swift b/HardcoreData/Observing/ManagedObjectController.swift index 8d9bde2..f28d048 100644 --- a/HardcoreData/Observing/ManagedObjectController.swift +++ b/HardcoreData/Observing/ManagedObjectController.swift @@ -87,7 +87,7 @@ public final class ManagedObjectController { */ public func addObserver(observer: U) { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to add a \(typeName(observer)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.") self.removeObserver(observer) @@ -155,7 +155,7 @@ public final class ManagedObjectController { */ public func removeObserver(observer: U) { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to remove a \(typeName(observer)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to remove an observer of type \(typeName(observer)) outside the main thread.") let nilValue: AnyObject? = nil setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeObject, inObject: observer) diff --git a/HardcoreData/Observing/ManagedObjectListController.swift b/HardcoreData/Observing/ManagedObjectListController.swift index bfc9824..5538ded 100644 --- a/HardcoreData/Observing/ManagedObjectListController.swift +++ b/HardcoreData/Observing/ManagedObjectListController.swift @@ -188,7 +188,7 @@ public final class ManagedObjectListController { */ public func addObserver(observer: U) { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to add a \(typeName(observer)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.") self.removeObserver(observer) @@ -231,7 +231,7 @@ public final class ManagedObjectListController { */ public func addObserver(observer: U) { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to add a \(typeName(observer)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.") self.removeObserver(observer) @@ -340,7 +340,7 @@ public final class ManagedObjectListController { */ public func addObserver(observer: U) { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to add a \(typeName(observer)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.") self.removeObserver(observer) @@ -478,7 +478,7 @@ public final class ManagedObjectListController { */ public func removeObserver(observer: U) { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to remove a \(typeName(observer)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to remove an observer of type \(typeName(observer)) outside the main thread.") let nilValue: AnyObject? = nil setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeList, inObject: observer) @@ -496,12 +496,13 @@ public final class ManagedObjectListController { // MARK: Internal - internal init(dataStack: DataStack, entity: T.Type, sectionedBy: SectionedBy?, fetchClauses: [FetchClause]) { + internal init(dataStack: DataStack, from: From, sectionedBy: SectionedBy?, fetchClauses: [FetchClause]) { let context = dataStack.mainContext let fetchRequest = NSFetchRequest() - fetchRequest.entity = context.entityDescriptionForEntityClass(entity) + from.applyToFetchRequest(fetchRequest, context: context) + fetchRequest.fetchLimit = 0 fetchRequest.resultType = .ManagedObjectResultType diff --git a/HardcoreData/Saving and Processing/AsynchronousDataTransaction.swift b/HardcoreData/Saving and Processing/AsynchronousDataTransaction.swift index 5ffa565..9a5f159 100644 --- a/HardcoreData/Saving and Processing/AsynchronousDataTransaction.swift +++ b/HardcoreData/Saving and Processing/AsynchronousDataTransaction.swift @@ -91,16 +91,16 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { // MARK: BaseDataTransaction /** - Creates a new `NSManagedObject` with the specified entity type. This method should not be used after the `commit()` method was already called once. + Creates a new `NSManagedObject` with the specified entity type. - :param: entity the `NSManagedObject` type to be created + :param: into the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration :returns: a new `NSManagedObject` instance of the specified entity type. */ - public override func create(entity: T.Type) -> T { + public override func create(into: Into) -> T { - HardcoreData.assert(!self.isCommitted, "Attempted to create an entity of type <\(entity)> from an already committed \(typeName(self)).") + HardcoreData.assert(!self.isCommitted, "Attempted to create an entity of type <\(T.self)> from an already committed \(typeName(self)).") - return super.create(entity) + return super.create(into) } /** diff --git a/HardcoreData/Saving and Processing/BaseDataTransaction.swift b/HardcoreData/Saving and Processing/BaseDataTransaction.swift index 1557973..5e35501 100644 --- a/HardcoreData/Saving and Processing/BaseDataTransaction.swift +++ b/HardcoreData/Saving and Processing/BaseDataTransaction.swift @@ -28,6 +28,86 @@ import CoreData import GCDKit +// MARK: - Into + +/** +A `Into` clause contains the destination entity and destination persistent store for a `create(...)` method. A common usage is to just indicate the entity: + + let person = transaction.create(Into(MyPersonEntity)) + +For cases where multiple `NSPersistentStore`'s contain the same entity, the destination configuration's name needs to be specified as well: + + let person = transaction.create(Into("Configuration1")) + +This helps the `NSManagedObjectContext` to determine which +*/ +public struct Into { + + // MARK: Public + + /** + Initializes an `Into` clause. + Sample Usage: + + let person = transaction.create(Into()) + */ + public init(){ + + self.configuration = nil + self.inferStoreIfPossible = true + } + + /** + Initializes an `Into` clause with the specified entity type. + Sample Usage: + + let person = transaction.create(Into(MyPersonEntity)) + + :param: entity the `NSManagedObject` type to be created + */ + public init(_ entity: T.Type) { + + self.configuration = nil + self.inferStoreIfPossible = true + } + + /** + Initializes an `Into` clause with the specified configuration. + Sample Usage: + + let person = transaction.create(Into("Configuration1")) + + :param: configuration the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. + */ + public init(_ configuration: String?) { + + self.configuration = configuration + self.inferStoreIfPossible = false + } + + /** + Initializes an `Into` clause with the specified entity type and configuration. + Sample Usage: + + let person = transaction.create(Into(MyPersonEntity.self, "Configuration1")) + + :param: entity the `NSManagedObject` type to be created + :param: configuration the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. + */ + public init(_ entity: T.Type, _ configuration: String?) { + + self.configuration = configuration + self.inferStoreIfPossible = false + } + + + // MARK: Internal + + internal let configuration: String? + internal let inferStoreIfPossible: Bool +} + + // MARK: - BaseDataTransaction /** @@ -48,14 +128,50 @@ public /*abstract*/ class BaseDataTransaction { /** Creates a new `NSManagedObject` with the specified entity type. - :param: entity the `NSManagedObject` type to be created + :param: into the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration :returns: a new `NSManagedObject` instance of the specified entity type. */ - public func create(entity: T.Type) -> T { + public func create(into: Into) -> T { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to create an entity of type <\(entity)> outside its designated queue.") + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to create an entity of type <\(T.self)> outside its designated queue.") - return T.createInContext(self.context) + let context = self.context + let object = T.createInContext(context) + + if into.inferStoreIfPossible { + + switch context.parentStack!.persistentStoreForEntityClass(T.self, configuration: nil, inferStoreIfPossible: true) { + + case (.Some(let persistentStore), _): + context.assignObject(object, toPersistentStore: persistentStore) + + case (.None, true): + HardcoreData.assert(false, "Attempted to create an entity of type \(typeName(object)) with ambiguous destination persistent store, but the configuration name was not specified.") + + default: + HardcoreData.assert(false, "Attempted to create an entity of type \(typeName(object)), but a destination persistent store containing the entity type could not be found.") + } + } + else { + + switch context.parentStack!.persistentStoreForEntityClass(T.self, configuration: into.configuration, inferStoreIfPossible: false) { + + case (.Some(let persistentStore), _): + context.assignObject(object, toPersistentStore: persistentStore) + + default: + if let configuration = into.configuration { + + HardcoreData.assert(false, "Attempted to create an entity of type \(typeName(object)) into the configuration \"\(configuration)\", which it doesn't belong to.") + } + else { + + HardcoreData.assert(false, "Attempted to create an entity of type \(typeName(object)) into the default configuration, which it doesn't belong to.") + } + } + } + + return object } /** diff --git a/HardcoreData/Saving and Processing/DataStack+Transaction.swift b/HardcoreData/Saving and Processing/DataStack+Transaction.swift index 7d3638a..90ae751 100644 --- a/HardcoreData/Saving and Processing/DataStack+Transaction.swift +++ b/HardcoreData/Saving and Processing/DataStack+Transaction.swift @@ -41,7 +41,7 @@ public extension DataStack { */ public func beginAsynchronous(closure: (transaction: AsynchronousDataTransaction) -> Void) { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to begin a transaction from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to begin a transaction from a \(typeName(self)) outside the main thread.") AsynchronousDataTransaction( mainContext: self.rootSavingContext, @@ -57,7 +57,7 @@ public extension DataStack { */ public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to begin a transaction from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to begin a transaction from a \(typeName(self)) outside the main thread.") return SynchronousDataTransaction( mainContext: self.rootSavingContext, @@ -72,7 +72,7 @@ public extension DataStack { */ public func beginDetached() -> DetachedDataTransaction { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to begin a transaction from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(NSThread.isMainThread(), "Attempted to begin a transaction from a \(typeName(self)) outside the main thread.") return DetachedDataTransaction( mainContext: self.rootSavingContext, diff --git a/HardcoreData/Saving and Processing/SynchronousDataTransaction.swift b/HardcoreData/Saving and Processing/SynchronousDataTransaction.swift index e04cf57..afb0ca0 100644 --- a/HardcoreData/Saving and Processing/SynchronousDataTransaction.swift +++ b/HardcoreData/Saving and Processing/SynchronousDataTransaction.swift @@ -70,16 +70,16 @@ public final class SynchronousDataTransaction: BaseDataTransaction { // MARK: BaseDataTransaction /** - Creates a new `NSManagedObject` with the specified entity type. This method should not be used after the `commit()` method was already called once. + Creates a new `NSManagedObject` with the specified entity type. - :param: entity the `NSManagedObject` type to be created + :param: into the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration :returns: a new `NSManagedObject` instance of the specified entity type. */ - public override func create(entity: T.Type) -> T { + public override func create(into: Into) -> T { - HardcoreData.assert(!self.isCommitted, "Attempted to create an entity of type <\(entity)> from an already committed \(typeName(self)).") + HardcoreData.assert(!self.isCommitted, "Attempted to create an entity of type <\(T.self)> from an already committed \(typeName(self)).") - return super.create(entity) + return super.create(into) } /** diff --git a/HardcoreData/Setting Up/DataStack.swift b/HardcoreData/Setting Up/DataStack.swift index 1bdb712..9d73923 100644 --- a/HardcoreData/Setting Up/DataStack.swift +++ b/HardcoreData/Setting Up/DataStack.swift @@ -28,6 +28,8 @@ import CoreData import GCDKit +private let defaultConfigurationName = "PF_DEFAULT_CONFIGURATION_NAME" + private let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first as! NSURL private let applicationName = ((NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData") @@ -81,14 +83,20 @@ public final class DataStack { self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel) self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator) self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext) - self.entityNameMapping = (managedObjectModel.entities as! [NSEntityDescription]).reduce([EntityClassNameType: EntityNameType]()) { (var mapping, entityDescription) in + + var entityNameMapping = [EntityClassNameType: EntityNameType]() + var entityConfigurationsMapping = [EntityClassNameType: Set]() + for entityDescription in managedObjectModel.entities as! [NSEntityDescription] { + let managedObjectClassName = entityDescription.managedObjectClassName + entityConfigurationsMapping[managedObjectClassName] = [] if let entityName = entityDescription.name { - mapping[entityDescription.managedObjectClassName] = entityName + entityNameMapping[managedObjectClassName] = entityName } - return mapping } + self.entityNameMapping = entityNameMapping + self.entityConfigurationsMapping = entityConfigurationsMapping self.rootSavingContext.parentStack = self } @@ -117,6 +125,7 @@ public final class DataStack { if let store = store { + self.updateMetadataForPersistentStore(store) return PersistentStoreResult(store) } @@ -140,7 +149,7 @@ public final class DataStack { Adds to the stack an SQLite store from the given SQLite file name. :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. 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: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true. :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. @@ -162,7 +171,7 @@ public final class DataStack { Adds to the stack an SQLite store from the given SQLite file URL. :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. 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: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true. :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. @@ -176,7 +185,7 @@ public final class DataStack { if store.type == NSSQLiteStoreType && isExistingStoreAutomigrating == automigrating - && store.configurationName == (configuration ?? "PF_DEFAULT_CONFIGURATION_NAME") { + && store.configurationName == (configuration ?? defaultConfigurationName) { return PersistentStoreResult(store) } @@ -218,6 +227,7 @@ public final class DataStack { if let store = store { + self.updateMetadataForPersistentStore(store) return PersistentStoreResult(store) } @@ -253,6 +263,7 @@ public final class DataStack { if let store = store { + self.updateMetadataForPersistentStore(store) return PersistentStoreResult(store) } } @@ -276,12 +287,77 @@ public final class DataStack { return self.entityNameMapping[NSStringFromClass(entityClass)] } + internal func persistentStoresForEntityClass(entityClass: NSManagedObject.Type) -> [NSPersistentStore]? { + + var returnValue: [NSPersistentStore]? = nil + self.storeMetadataUpdateQueue.barrierSync { + + let configurationsForEntity = self.entityConfigurationsMapping[NSStringFromClass(entityClass)] ?? [] + returnValue = map(configurationsForEntity) { + + return self.configurationStoreMapping[$0]! + } + } + return returnValue + } + + internal func persistentStoreForEntityClass(entityClass: NSManagedObject.Type, configuration: String?, inferStoreIfPossible: Bool) -> (store: NSPersistentStore?, isAmbiguous: Bool) { + + var returnValue: (store: NSPersistentStore?, isAmbiguous: Bool) = (store: nil, isAmbiguous: false) + self.storeMetadataUpdateQueue.barrierSync { + + let configurationsForEntity = self.entityConfigurationsMapping[NSStringFromClass(entityClass)] ?? [] + if let configuration = configuration { + + if configurationsForEntity.contains(configuration) { + + returnValue = (store: self.configurationStoreMapping[configuration], isAmbiguous: false) + return + } + else if !inferStoreIfPossible { + + return + } + } + + switch configurationsForEntity.count { + + case 0: + return + + case 1 where inferStoreIfPossible: + returnValue = (store: self.configurationStoreMapping[configurationsForEntity.first!], isAmbiguous: false) + + default: + returnValue = (store: nil, isAmbiguous: true) + } + } + 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.hardcoreData.persistentStoreBarrierQueue") + private var configurationStoreMapping = [ConfigurationNameType: NSPersistentStore]() + private var entityConfigurationsMapping = [EntityClassNameType: Set]() + + private func updateMetadataForPersistentStore(persistentStore: NSPersistentStore) { + + self.storeMetadataUpdateQueue.barrierAsync { + + let configurationName = persistentStore.configurationName + self.configurationStoreMapping[configurationName] = persistentStore + for entityDescription in (self.coordinator.managedObjectModel.entitiesForConfiguration(configurationName) as? [NSEntityDescription] ?? []) { + + self.entityConfigurationsMapping[entityDescription.managedObjectClassName]?.insert(configurationName) + } + } + } } diff --git a/HardcoreDataDemo/HardcoreDataDemo.xcodeproj/project.pbxproj b/HardcoreDataDemo/HardcoreDataDemo.xcodeproj/project.pbxproj index 010ec3f..b89babf 100644 --- a/HardcoreDataDemo/HardcoreDataDemo.xcodeproj/project.pbxproj +++ b/HardcoreDataDemo/HardcoreDataDemo.xcodeproj/project.pbxproj @@ -11,24 +11,25 @@ B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */; }; B503FAE11AFDC71700F90881 /* Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADD1AFDC71700F90881 /* Palette.swift */; }; B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */; }; + B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977D81B120B80003D50A5 /* ObserversViewController.swift */; }; + B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977DC1B120F3B003D50A5 /* TransactionsDemoViewController.swift */; }; + B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B52977DE1B120F83003D50A5 /* MapKit.framework */; }; + B52977E11B120F8A003D50A5 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B52977E01B120F8A003D50A5 /* CoreLocation.framework */; }; + B52977E41B121635003D50A5 /* Place.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977E31B121635003D50A5 /* Place.swift */; }; B54AAD4F1AF4D26E00848AE0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54AAD4E1AF4D26E00848AE0 /* AppDelegate.swift */; }; B54AAD521AF4D26E00848AE0 /* HardcoreDataDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B54AAD501AF4D26E00848AE0 /* HardcoreDataDemo.xcdatamodeld */; }; B54AAD591AF4D26E00848AE0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD571AF4D26E00848AE0 /* Main.storyboard */; }; B54AAD5B1AF4D26E00848AE0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */; }; B54AAD5E1AF4D26E00848AE0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */; }; - B54AAD6A1AF4D26E00848AE0 /* HardcoreDataDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54AAD691AF4D26E00848AE0 /* HardcoreDataDemoTests.swift */; }; + B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */; }; + B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3311B11DF3200F4F0C6 /* UserAccount.swift */; }; B583A9201AF5F542001F76AF /* HardcoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* HardcoreData.framework */; }; B583A9211AF5F542001F76AF /* HardcoreData.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* HardcoreData.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 */ - B54AAD641AF4D26E00848AE0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = B54AAD411AF4D26E00848AE0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B54AAD481AF4D26E00848AE0; - remoteInfo = HardcoreDataDemo; - }; B583A91A1AF5F4F4001F76AF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B583A9141AF5F4F3001F76AF /* HardcoreData.xcodeproj */; @@ -78,6 +79,11 @@ B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectObserverDemoViewController.swift; sourceTree = ""; }; B503FADD1AFDC71700F90881 /* Palette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Palette.swift; sourceTree = ""; }; B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaletteTableViewCell.swift; sourceTree = ""; }; + B52977D81B120B80003D50A5 /* ObserversViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserversViewController.swift; sourceTree = ""; }; + B52977DC1B120F3B003D50A5 /* TransactionsDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionsDemoViewController.swift; sourceTree = ""; }; + B52977DE1B120F83003D50A5 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; + B52977E01B120F8A003D50A5 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; + B52977E31B121635003D50A5 /* Place.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Place.swift; sourceTree = ""; }; B54AAD491AF4D26E00848AE0 /* HardcoreDataDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HardcoreDataDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; B54AAD4D1AF4D26E00848AE0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B54AAD4E1AF4D26E00848AE0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -85,11 +91,12 @@ B54AAD581AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; B54AAD5D1AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; - B54AAD631AF4D26E00848AE0 /* HardcoreDataDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HardcoreDataDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - B54AAD681AF4D26E00848AE0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B54AAD691AF4D26E00848AE0 /* HardcoreDataDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardcoreDataDemoTests.swift; sourceTree = ""; }; + 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 = ""; }; B583A9141AF5F4F3001F76AF /* HardcoreData.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = HardcoreData.xcodeproj; path = ../HardcoreData.xcodeproj; sourceTree = ""; }; B583A9251AF5F547001F76AF /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; name = GCDKit.framework; path = "/Users/johnestropia/Library/Developer/Xcode/DerivedData/HardcoreDataDemo-ftknhsqfpsthfogvisxisgpbbhsj/Build/Products/Debug-iphoneos/GCDKit.framework"; sourceTree = ""; }; + 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 */ @@ -97,23 +104,19 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B52977E11B120F8A003D50A5 /* CoreLocation.framework in Frameworks */, + B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */, B583A9201AF5F542001F76AF /* HardcoreData.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - B54AAD601AF4D26E00848AE0 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ B503FADA1AFDC71700F90881 /* List and Object Observers Demo */ = { isa = PBXGroup; children = ( + B52977D81B120B80003D50A5 /* ObserversViewController.swift */, B503FADB1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift */, B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */, B503FADD1AFDC71700F90881 /* Palette.swift */, @@ -122,13 +125,31 @@ path = "List and Object Observers Demo"; sourceTree = ""; }; + B52977DB1B120F2C003D50A5 /* Transactions Demo */ = { + isa = PBXGroup; + children = ( + B52977E31B121635003D50A5 /* Place.swift */, + B52977DC1B120F3B003D50A5 /* TransactionsDemoViewController.swift */, + ); + path = "Transactions Demo"; + sourceTree = ""; + }; + B52977E21B120F90003D50A5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + B583A9141AF5F4F3001F76AF /* HardcoreData.xcodeproj */, + B52977E01B120F8A003D50A5 /* CoreLocation.framework */, + B52977DE1B120F83003D50A5 /* MapKit.framework */, + B583A9251AF5F547001F76AF /* GCDKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; B54AAD401AF4D26E00848AE0 = { isa = PBXGroup; children = ( - B583A9251AF5F547001F76AF /* GCDKit.framework */, - B583A9141AF5F4F3001F76AF /* HardcoreData.xcodeproj */, + B52977E21B120F90003D50A5 /* Frameworks */, B54AAD4B1AF4D26E00848AE0 /* HardcoreDataDemo */, - B54AAD661AF4D26E00848AE0 /* HardcoreDataDemoTests */, B54AAD4A1AF4D26E00848AE0 /* Products */, ); sourceTree = ""; @@ -137,7 +158,6 @@ isa = PBXGroup; children = ( B54AAD491AF4D26E00848AE0 /* HardcoreDataDemo.app */, - B54AAD631AF4D26E00848AE0 /* HardcoreDataDemoTests.xctest */, ); name = Products; sourceTree = ""; @@ -146,7 +166,9 @@ isa = PBXGroup; children = ( B54AAD4E1AF4D26E00848AE0 /* AppDelegate.swift */, + B566E3271B117AE700F4F0C6 /* Stack Setup Demo */, B503FADA1AFDC71700F90881 /* List and Object Observers Demo */, + B52977DB1B120F2C003D50A5 /* Transactions Demo */, B54AAD571AF4D26E00848AE0 /* Main.storyboard */, B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */, B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */, @@ -164,21 +186,15 @@ name = "Supporting Files"; sourceTree = ""; }; - B54AAD661AF4D26E00848AE0 /* HardcoreDataDemoTests */ = { + B566E3271B117AE700F4F0C6 /* Stack Setup Demo */ = { isa = PBXGroup; children = ( - B54AAD691AF4D26E00848AE0 /* HardcoreDataDemoTests.swift */, - B54AAD671AF4D26E00848AE0 /* Supporting Files */, + B5E724101B11F994006FB83F /* FacebookAccount.swift */, + B5E7240E1B11F993006FB83F /* TwitterAccount.swift */, + B566E3311B11DF3200F4F0C6 /* UserAccount.swift */, + B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */, ); - path = HardcoreDataDemoTests; - sourceTree = ""; - }; - B54AAD671AF4D26E00848AE0 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - B54AAD681AF4D26E00848AE0 /* Info.plist */, - ); - name = "Supporting Files"; + path = "Stack Setup Demo"; sourceTree = ""; }; B583A9151AF5F4F3001F76AF /* Products */ = { @@ -213,24 +229,6 @@ productReference = B54AAD491AF4D26E00848AE0 /* HardcoreDataDemo.app */; productType = "com.apple.product-type.application"; }; - B54AAD621AF4D26E00848AE0 /* HardcoreDataDemoTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = B54AAD701AF4D26E00848AE0 /* Build configuration list for PBXNativeTarget "HardcoreDataDemoTests" */; - buildPhases = ( - B54AAD5F1AF4D26E00848AE0 /* Sources */, - B54AAD601AF4D26E00848AE0 /* Frameworks */, - B54AAD611AF4D26E00848AE0 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - B54AAD651AF4D26E00848AE0 /* PBXTargetDependency */, - ); - name = HardcoreDataDemoTests; - productName = HardcoreDataDemoTests; - productReference = B54AAD631AF4D26E00848AE0 /* HardcoreDataDemoTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -243,10 +241,6 @@ B54AAD481AF4D26E00848AE0 = { CreatedOnToolsVersion = 6.3; }; - B54AAD621AF4D26E00848AE0 = { - CreatedOnToolsVersion = 6.3; - TestTargetID = B54AAD481AF4D26E00848AE0; - }; }; }; buildConfigurationList = B54AAD441AF4D26E00848AE0 /* Build configuration list for PBXProject "HardcoreDataDemo" */; @@ -269,7 +263,6 @@ projectRoot = ""; targets = ( B54AAD481AF4D26E00848AE0 /* HardcoreDataDemo */, - B54AAD621AF4D26E00848AE0 /* HardcoreDataDemoTests */, ); }; /* End PBXProject section */ @@ -302,13 +295,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - B54AAD611AF4D26E00848AE0 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -316,7 +302,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */, + B52977E41B121635003D50A5 /* Place.swift in Sources */, B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */, + B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */, + B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */, + B5E724111B11F994006FB83F /* FacebookAccount.swift in Sources */, + B5E7240F1B11F993006FB83F /* TwitterAccount.swift in Sources */, + B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */, B54AAD521AF4D26E00848AE0 /* HardcoreDataDemo.xcdatamodeld in Sources */, B503FAE11AFDC71700F90881 /* Palette.swift in Sources */, B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */, @@ -325,22 +318,9 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - B54AAD5F1AF4D26E00848AE0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - B54AAD6A1AF4D26E00848AE0 /* HardcoreDataDemoTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - B54AAD651AF4D26E00848AE0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = B54AAD481AF4D26E00848AE0 /* HardcoreDataDemo */; - targetProxy = B54AAD641AF4D26E00848AE0 /* PBXContainerItemProxy */; - }; B583A91F1AF5F512001F76AF /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = HardcoreData; @@ -463,6 +443,7 @@ "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/HardcoreDataDemo-ftknhsqfpsthfogvisxisgpbbhsj/Build/Products/Debug-iphoneos", ); INFOPLIST_FILE = HardcoreDataDemo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; }; @@ -477,45 +458,12 @@ "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/HardcoreDataDemo-ftknhsqfpsthfogvisxisgpbbhsj/Build/Products/Debug-iphoneos", ); INFOPLIST_FILE = HardcoreDataDemo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; - B54AAD711AF4D26E00848AE0 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = HardcoreDataDemoTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HardcoreDataDemo.app/HardcoreDataDemo"; - }; - name = Debug; - }; - B54AAD721AF4D26E00848AE0 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); - INFOPLIST_FILE = HardcoreDataDemoTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HardcoreDataDemo.app/HardcoreDataDemo"; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -537,15 +485,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - B54AAD701AF4D26E00848AE0 /* Build configuration list for PBXNativeTarget "HardcoreDataDemoTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B54AAD711AF4D26E00848AE0 /* Debug */, - B54AAD721AF4D26E00848AE0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ /* Begin XCVersionGroup section */ diff --git a/HardcoreDataDemo/HardcoreDataDemo/AppDelegate.swift b/HardcoreDataDemo/HardcoreDataDemo/AppDelegate.swift index 7a314ec..05b3abb 100644 --- a/HardcoreDataDemo/HardcoreDataDemo/AppDelegate.swift +++ b/HardcoreDataDemo/HardcoreDataDemo/AppDelegate.swift @@ -7,7 +7,6 @@ // import UIKit -import HardcoreData // MARK: - AppDelegate @@ -21,7 +20,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - HardcoreData.addSQLiteStore(resetStoreOnMigrationFailure: true) return true } } diff --git a/HardcoreDataDemo/HardcoreDataDemo/Base.lproj/Main.storyboard b/HardcoreDataDemo/HardcoreDataDemo/Base.lproj/Main.storyboard index ddee4b5..d2257b0 100644 --- a/HardcoreDataDemo/HardcoreDataDemo/Base.lproj/Main.storyboard +++ b/HardcoreDataDemo/HardcoreDataDemo/Base.lproj/Main.storyboard @@ -6,6 +6,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -22,7 +103,7 @@ - + + + @@ -65,7 +149,7 @@ - + + + @@ -261,7 +348,7 @@ - + @@ -461,6 +548,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HardcoreDataDemo/HardcoreDataDemo/HardcoreDataDemo.xcdatamodeld/HardcoreDataDemo.xcdatamodel/contents b/HardcoreDataDemo/HardcoreDataDemo/HardcoreDataDemo.xcdatamodeld/HardcoreDataDemo.xcdatamodel/contents index 6b57a6b..e9170d9 100644 --- a/HardcoreDataDemo/HardcoreDataDemo/HardcoreDataDemo.xcdatamodeld/HardcoreDataDemo.xcdatamodel/contents +++ b/HardcoreDataDemo/HardcoreDataDemo/HardcoreDataDemo.xcdatamodeld/HardcoreDataDemo.xcdatamodel/contents @@ -7,7 +7,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HardcoreDataDemo/HardcoreDataDemo/List and Object Observers Demo/ObjectListObserverDemoViewController.swift b/HardcoreDataDemo/HardcoreDataDemo/List and Object Observers Demo/ObjectListObserverDemoViewController.swift index 819a923..e51373c 100644 --- a/HardcoreDataDemo/HardcoreDataDemo/List and Object Observers Demo/ObjectListObserverDemoViewController.swift +++ b/HardcoreDataDemo/HardcoreDataDemo/List and Object Observers Demo/ObjectListObserverDemoViewController.swift @@ -10,13 +10,22 @@ import UIKit import HardcoreData -struct Shared { +private struct Static { - static let palettes = HardcoreData.observeSectionedList( - From(Palette), - SectionedBy("colorName"), - OrderBy(.Ascending("hue")) - ) + static let palettes: ManagedObjectListController = { + + HardcoreData.addSQLiteStore( + "ColorsDemo.sqlite", + configuration: "ObservingDemo", + resetStoreOnMigrationFailure: true + ) + + return HardcoreData.observeSectionedList( + From(Palette), + SectionedBy("colorName"), + OrderBy(.Ascending("hue")) + ) + }() } @@ -28,7 +37,7 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject deinit { - Shared.palettes.removeObserver(self) + Static.palettes.removeObserver(self) } @@ -52,7 +61,7 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject action: "addBarButtonItemTouched:" ) - Shared.palettes.addObserver(self) + Static.palettes.addObserver(self) } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { @@ -74,19 +83,19 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject override func numberOfSectionsInTableView(tableView: UITableView) -> Int { - return Shared.palettes.numberOfSections() + return Static.palettes.numberOfSections() } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return Shared.palettes.numberOfObjectsInSection(section) + return Static.palettes.numberOfObjectsInSection(section) } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("PaletteTableViewCell") as! PaletteTableViewCell - let palette = Shared.palettes[indexPath] + let palette = Static.palettes[indexPath] cell.colorView?.backgroundColor = palette.color cell.label?.text = palette.colorText @@ -102,7 +111,7 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject self.performSegueWithIdentifier( "ObjectObserverDemoViewController", - sender: Shared.palettes[indexPath] + sender: Static.palettes[indexPath] ) } @@ -111,7 +120,7 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject switch editingStyle { case .Delete: - let palette = Shared.palettes[indexPath] + let palette = Static.palettes[indexPath] HardcoreData.beginAsynchronous{ (transaction) -> Void in transaction.delete(palette) @@ -125,7 +134,7 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return Shared.palettes.sectionInfoAtIndex(section).name + return Static.palettes.sectionInfoAtIndex(section).name } @@ -158,7 +167,7 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject if let cell = self.tableView.cellForRowAtIndexPath(indexPath) as? PaletteTableViewCell { - let palette = Shared.palettes[indexPath] + let palette = Static.palettes[indexPath] cell.colorView?.backgroundColor = palette.color cell.label?.text = palette.colorText } @@ -199,7 +208,7 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject HardcoreData.beginAsynchronous { (transaction) -> Void in - let palette = transaction.create(Palette) + let palette = transaction.create(Into(Palette)) palette.setInitialValues() transaction.commit() diff --git a/HardcoreDataDemo/HardcoreDataDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift b/HardcoreDataDemo/HardcoreDataDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift index edf1ce9..5e9e160 100644 --- a/HardcoreDataDemo/HardcoreDataDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift +++ b/HardcoreDataDemo/HardcoreDataDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift @@ -53,7 +53,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver HardcoreData.beginSynchronous { (transaction) -> Void in - let palette = transaction.create(Palette) + let palette = transaction.create(Into(Palette)) palette.setInitialValues() transaction.commit() diff --git a/HardcoreDataDemo/HardcoreDataDemo/List and Object Observers Demo/ObserversViewController.swift b/HardcoreDataDemo/HardcoreDataDemo/List and Object Observers Demo/ObserversViewController.swift new file mode 100644 index 0000000..a8bf26b --- /dev/null +++ b/HardcoreDataDemo/HardcoreDataDemo/List and Object Observers Demo/ObserversViewController.swift @@ -0,0 +1,30 @@ +// +// ObserversViewController.swift +// HardcoreDataDemo +// +// Created by John Rommel Estropia on 2015/05/24. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import UIKit + + +// MARK: - ObserversViewController + +class ObserversViewController: UIViewController { + + // MARK: UIViewController + + override func viewDidAppear(animated: Bool) { + + super.viewDidAppear(animated) + + let alert = UIAlertController( + title: "Observers Demo", + message: "This demo shows how to observe changes to a list of objects. The top and bottom view controllers both observe a single shared \"ManagedObjectListController\" instance.\n\nTap on a row to see another demo that shows how to observe changes to a single object using a \"ManagedObjectController\".", + preferredStyle: .Alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil)) + self.presentViewController(alert, animated: true, completion: nil) + } +} diff --git a/HardcoreDataDemo/HardcoreDataDemo/Stack Setup Demo/FacebookAccount.swift b/HardcoreDataDemo/HardcoreDataDemo/Stack Setup Demo/FacebookAccount.swift new file mode 100644 index 0000000..92f6b3c --- /dev/null +++ b/HardcoreDataDemo/HardcoreDataDemo/Stack Setup Demo/FacebookAccount.swift @@ -0,0 +1,15 @@ +// +// FacebookAccount.swift +// HardcoreDataDemo +// +// 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 { } diff --git a/HardcoreDataDemo/HardcoreDataDemo/Stack Setup Demo/StackSetupDemoViewController.swift b/HardcoreDataDemo/HardcoreDataDemo/Stack Setup Demo/StackSetupDemoViewController.swift new file mode 100644 index 0000000..6562db8 --- /dev/null +++ b/HardcoreDataDemo/HardcoreDataDemo/Stack Setup Demo/StackSetupDemoViewController.swift @@ -0,0 +1,190 @@ +// +// StackSetupDemoViewController.swift +// HardcoreDataDemo +// +// Created by John Rommel Estropia on 2015/05/24. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import UIKit +import HardcoreData + + +private struct Static { + + static let johnConfiguration = "SetupDemo_John" + static let janeConfiguration = "SetupDemo_Jane" + + static let facebookStack: DataStack = { + + let dataStack = DataStack(modelName: "HardcoreDataDemo") + dataStack.addSQLiteStore( + "AccountsDemo_FB_John.sqlite", + configuration: johnConfiguration, + resetStoreOnMigrationFailure: true + ) + dataStack.addSQLiteStore( + "AccountsDemo_FB_Jane.sqlite", + configuration: janeConfiguration, + resetStoreOnMigrationFailure: true + ) + + dataStack.beginSynchronous { (transaction) -> Void in + + transaction.deleteAll(From(johnConfiguration)) + transaction.deleteAll(From(janeConfiguration)) + + let account1 = transaction.create(Into(johnConfiguration)) + account1.accountType = "Facebook" + account1.name = "John Smith HCD" + account1.friends = 42 + + let account2 = transaction.create(Into(janeConfiguration)) + account2.accountType = "Facebook" + account2.name = "Jane Doe HCD" + account2.friends = 314 + + transaction.commit() + } + + return dataStack + }() + + static let twitterStack: DataStack = { + + let dataStack = DataStack(modelName: "HardcoreDataDemo") + dataStack.addSQLiteStore( + "AccountsDemo_TW_John.sqlite", + configuration: johnConfiguration, + resetStoreOnMigrationFailure: true + ) + dataStack.addSQLiteStore( + "AccountsDemo_TW_Jane.sqlite", + configuration: janeConfiguration, + resetStoreOnMigrationFailure: true + ) + + dataStack.beginSynchronous { (transaction) -> Void in + + transaction.deleteAll(From(johnConfiguration)) + transaction.deleteAll(From(janeConfiguration)) + + let account1 = transaction.create(Into(johnConfiguration)) + account1.accountType = "Twitter" + account1.name = "#johnsmith_hcd" + account1.friends = 7 + + let account2 = transaction.create(Into(janeConfiguration)) + account2.accountType = "Twitter" + account2.name = "#janedoe_hcd" + account2.friends = 100 + + transaction.commit() + } + + return dataStack + }() +} + + + + +// MARK: - StackSetupDemoViewController + +class StackSetupDemoViewController: UITableViewController { + + let accounts = [ + [ + Static.facebookStack.fetchOne(From(Static.johnConfiguration))!, + Static.facebookStack.fetchOne(From(Static.janeConfiguration))! + ], + [ + Static.twitterStack.fetchOne(From(Static.johnConfiguration))!, + Static.twitterStack.fetchOne(From(Static.janeConfiguration))! + ] + ] + + + // MARK: UIViewController + + override func viewWillAppear(animated: Bool) { + + super.viewWillAppear(animated) + + self.tableView.reloadData() + + let indexPath = NSIndexPath(forRow: 0, inSection: 0) + self.tableView.selectRowAtIndexPath(indexPath, animated: false, scrollPosition: .None) + self.updateDetailsWithAccount(self.accounts[indexPath.section][indexPath.row]) + } + + override func viewDidAppear(animated: Bool) { + + super.viewDidAppear(animated) + + let alert = UIAlertController( + title: "Setup Demo", + message: "This demo shows how to initialize 2 DataStacks with 2 configurations each, for a total of 4 SQLite files, each with 1 instance of a \"UserAccount\" entity.", + preferredStyle: .Alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil)) + self.presentViewController(alert, animated: true, completion: nil) + } + + + // MARK: UITableViewDataSource + + override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + + return self.accounts.count + } + + override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + return self.accounts[section].count + } + + override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell") as! UITableViewCell + + let account = self.accounts[indexPath.section][indexPath.row] + cell.textLabel?.text = account.name + cell.detailTextLabel?.text = "\(account.friends) friends" + + return cell + } + + + // MARK: UITableViewDelegate + + override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + + let account = self.accounts[indexPath.section][indexPath.row] + self.updateDetailsWithAccount(account) + } + + override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + + switch section { + + case 0: return "Facebook Accounts" + case 1: return "Twitter Accounts" + default: return nil + } + } + + + // MARK: Private + + @IBOutlet weak var accountTypeLabel: UILabel? + @IBOutlet weak var nameLabel: UILabel? + @IBOutlet weak var friendsLabel: UILabel? + + func updateDetailsWithAccount(account: UserAccount) { + + self.accountTypeLabel?.text = account.accountType + self.nameLabel?.text = account.name + self.friendsLabel?.text = "\(account.friends) friends" + } +} diff --git a/HardcoreDataDemo/HardcoreDataDemo/Stack Setup Demo/TwitterAccount.swift b/HardcoreDataDemo/HardcoreDataDemo/Stack Setup Demo/TwitterAccount.swift new file mode 100644 index 0000000..242cd43 --- /dev/null +++ b/HardcoreDataDemo/HardcoreDataDemo/Stack Setup Demo/TwitterAccount.swift @@ -0,0 +1,15 @@ +// +// TwitterAccount.swift +// HardcoreDataDemo +// +// 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 { } diff --git a/HardcoreDataDemo/HardcoreDataDemo/Stack Setup Demo/UserAccount.swift b/HardcoreDataDemo/HardcoreDataDemo/Stack Setup Demo/UserAccount.swift new file mode 100644 index 0000000..7b64d17 --- /dev/null +++ b/HardcoreDataDemo/HardcoreDataDemo/Stack Setup Demo/UserAccount.swift @@ -0,0 +1,20 @@ +// +// UserAccount.swift +// HardcoreDataDemo +// +// Created by John Rommel Estropia on 2015/05/24. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import Foundation +import CoreData + + +// MARK: - UserAccount + +class UserAccount: NSManagedObject { + + @NSManaged var accountType: String? + @NSManaged var name: String? + @NSManaged var friends: Int32 +} diff --git a/HardcoreDataDemo/HardcoreDataDemo/Transactions Demo/Place.swift b/HardcoreDataDemo/HardcoreDataDemo/Transactions Demo/Place.swift new file mode 100644 index 0000000..570a83d --- /dev/null +++ b/HardcoreDataDemo/HardcoreDataDemo/Transactions Demo/Place.swift @@ -0,0 +1,50 @@ +// +// Place.swift +// HardcoreDataDemo +// +// Created by John Rommel Estropia on 2015/05/24. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import Foundation +import CoreData +import MapKit + + +// MARK: - Place + +class Place: NSManagedObject, MKAnnotation { + + @NSManaged var latitude: Double + @NSManaged var longitude: Double + @NSManaged var title: String? + @NSManaged var subtitle: String? + + func setInitialValues() { + + self.latitude = Double(arc4random_uniform(180)) - 90 + self.longitude = Double(arc4random_uniform(360)) - 180 + self.title = "\(self.latitude), \(self.longitude)" + self.subtitle = nil + } + + // MARK: MKAnnotation + + var coordinate: CLLocationCoordinate2D { + + get { + + return CLLocationCoordinate2DMake( + self.latitude, + self.longitude + ) + } + set { + + self.latitude = newValue.latitude + self.longitude = newValue.longitude + self.title = "\(self.latitude), \(self.longitude)" + self.subtitle = nil + } + } +} diff --git a/HardcoreDataDemo/HardcoreDataDemo/Transactions Demo/TransactionsDemoViewController.swift b/HardcoreDataDemo/HardcoreDataDemo/Transactions Demo/TransactionsDemoViewController.swift new file mode 100644 index 0000000..bf857de --- /dev/null +++ b/HardcoreDataDemo/HardcoreDataDemo/Transactions Demo/TransactionsDemoViewController.swift @@ -0,0 +1,208 @@ +// +// TransactionsDemoViewController.swift +// HardcoreDataDemo +// +// Created by John Rommel Estropia on 2015/05/24. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import UIKit +import CoreLocation +import MapKit +import AddressBookUI +import HardcoreData +import GCDKit + + +private struct Static { + + static let placeController: ManagedObjectController = { + + HardcoreData.addSQLiteStore( + "PlaceDemo.sqlite", + configuration: "TransactionsDemo", + resetStoreOnMigrationFailure: true + ) + + var place = HardcoreData.fetchOne(From(Place)) + if place == nil { + + HardcoreData.beginSynchronous { (transaction) -> Void in + + let place = transaction.create(Into(Place)) + place.setInitialValues() + + transaction.commit() + } + place = HardcoreData.fetchOne(From(Place)) + } + + return HardcoreData.observeObject(place!) + }() +} + + +// MARK: - TransactionsDemoViewController + +class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, ManagedObjectObserver { + + // MARK: NSObject + + deinit { + + Static.placeController.removeObserver(self) + } + + + // MARK: UIViewController + + override func viewDidLoad() { + + super.viewDidLoad() + + let longPressGesture = UILongPressGestureRecognizer(target: self, action: "longPressGestureRecognized:") + self.mapView?.addGestureRecognizer(longPressGesture) + + Static.placeController.addObserver(self) + + self.navigationItem.rightBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .Refresh, + target: self, + action: "refreshButtonTapped:" + ) + } + + override func viewDidAppear(animated: Bool) { + + super.viewDidAppear(animated) + + let alert = UIAlertController( + title: "Observers Demo", + message: "This demo shows how to use the 3 types of transactions to save updates: synchronous, asynchronous, and detached. Long-tap on the map to change the pin location.", + preferredStyle: .Alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil)) + self.presentViewController(alert, animated: true, completion: nil) + } + + override func viewWillAppear(animated: Bool) { + + super.viewWillAppear(animated) + + if let mapView = self.mapView, let place = Static.placeController.object { + + mapView.addAnnotation(place) + mapView.setCenterCoordinate(place.coordinate, animated: false) + mapView.selectAnnotation(place, animated: false) + } + } + + + // MARK: MKMapViewDelegate + + func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! { + + let identifier = "MKAnnotationView" + var annotationView: MKPinAnnotationView! = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier) as? MKPinAnnotationView + if annotationView == nil { + + annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier) + annotationView.enabled = true + annotationView.canShowCallout = true + annotationView.animatesDrop = true + } + else { + + annotationView.annotation = annotation + } + + return annotationView + } + + + // MARK: ManagedObjectObserver + + func managedObjectWillUpdate(objectController: ManagedObjectController, object: Place) { + + // none + } + + func managedObjectWasUpdated(objectController: ManagedObjectController, object: Place, changedPersistentKeys: Set) { + + if let mapView = self.mapView { + + mapView.removeAnnotations(mapView.annotations ?? []) + mapView.addAnnotation(object) + mapView.setCenterCoordinate(object.coordinate, animated: true) + mapView.selectAnnotation(object, animated: true) + + if changedPersistentKeys.contains("latitude") || changedPersistentKeys.contains("longitude") { + + self.geocodePlace(object) + } + } + } + + func managedObjectWasDeleted(objectController: ManagedObjectController, object: Place) { + + // none + } + + + // MARK: Private + + var geocoder: CLGeocoder? + + @IBOutlet weak var mapView: MKMapView? + + @IBAction dynamic func longPressGestureRecognized(sender: AnyObject?) { + + if let mapView = self.mapView, let gesture = sender as? UILongPressGestureRecognizer where gesture.state == .Began { + + HardcoreData.beginAsynchronous { (transaction) -> Void in + + let place = transaction.fetch(Static.placeController.object) + place?.coordinate = mapView.convertPoint( + gesture.locationInView(mapView), + toCoordinateFromView: mapView + ) + transaction.commit { (_) -> Void in } + } + } + } + + @IBAction dynamic func refreshButtonTapped(sender: AnyObject?) { + + HardcoreData.beginSynchronous { (transaction) -> Void in + + let place = transaction.fetch(Static.placeController.object) + place?.setInitialValues() + transaction.commit() + } + } + + func geocodePlace(place: Place) { + + let transaction = HardcoreData.beginDetached() + + self.geocoder?.cancelGeocode() + + var geocoder = CLGeocoder() + self.geocoder = geocoder + geocoder.reverseGeocodeLocation( + CLLocation(latitude: place.latitude, longitude: place.longitude), + completionHandler: { [weak self] (placemarks, error) -> Void in + + if let strongSelf = self, let placemark = (placemarks as? [CLPlacemark])?.first { + + let place = transaction.fetch(Static.placeController.object) + place?.title = placemark.name + place?.subtitle = ABCreateStringWithAddressDictionary(placemark.addressDictionary, true) + transaction.commit { (_) -> Void in } + } + + self?.geocoder = nil + } + ) + } +} diff --git a/HardcoreDataDemo/HardcoreDataDemoTests/HardcoreDataDemoTests.swift b/HardcoreDataDemo/HardcoreDataDemoTests/HardcoreDataDemoTests.swift deleted file mode 100644 index d506eb4..0000000 --- a/HardcoreDataDemo/HardcoreDataDemoTests/HardcoreDataDemoTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// HardcoreDataDemoTests.swift -// HardcoreDataDemoTests -// -// Created by John Rommel Estropia on 2015/05/02. -// Copyright (c) 2015 John Rommel Estropia. All rights reserved. -// - -import UIKit -import XCTest - -class HardcoreDataDemoTests: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testExample() { - // This is an example of a functional test case. - XCTAssert(true, "Pass") - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measureBlock() { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/HardcoreDataDemo/HardcoreDataDemoTests/Info.plist b/HardcoreDataDemo/HardcoreDataDemoTests/Info.plist deleted file mode 100644 index e047843..0000000 --- a/HardcoreDataDemo/HardcoreDataDemoTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - com.johnestropia.$(PRODUCT_NAME:rfc1034identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/HardcoreDataTests/HardcoreDataTests.swift b/HardcoreDataTests/HardcoreDataTests.swift index 284ab56..4bf39da 100644 --- a/HardcoreDataTests/HardcoreDataTests.swift +++ b/HardcoreDataTests/HardcoreDataTests.swift @@ -70,31 +70,31 @@ class HardcoreDataTests: XCTestCase { let createExpectation = self.expectationWithDescription("Entity creation") HardcoreData.beginAsynchronous { (transaction) -> Void in - let obj1 = transaction.create(TestEntity1) + let obj1 = transaction.create(Into(TestEntity1)) obj1.testEntityID = 1 obj1.testString = "lololol" obj1.testNumber = 42 obj1.testDate = NSDate() let count = transaction.queryValue( - From(TestEntity1), + From(), Select(.Count("testNumber")) ) XCTAssertTrue(count == 0, "count == 0 (actual: \(count))") // counts only objects in store - let obj2 = transaction.create(TestEntity2) + let obj2 = transaction.create(Into()) obj2.testEntityID = 2 obj2.testString = "hahaha" obj2.testNumber = 100 obj2.testDate = NSDate() - let obj3 = transaction.create(TestEntity2) + let obj3 = transaction.create(Into("Config2")) obj3.testEntityID = 3 obj3.testString = "hahaha" obj3.testNumber = 90 obj3.testDate = NSDate() - let obj4 = transaction.create(TestEntity2) + let obj4 = transaction.create(Into(TestEntity2.self, "Config2")) obj4.testEntityID = 5 obj4.testString = "hohoho" obj4.testNumber = 80 @@ -103,14 +103,14 @@ class HardcoreDataTests: XCTestCase { transaction.beginSynchronous { (transaction) -> Void in - let obj4 = transaction.create(TestEntity2) + let obj4 = transaction.create(Into()) obj4.testEntityID = 4 obj4.testString = "hehehehe" obj4.testNumber = 80 obj4.testDate = NSDate() let objs4test = transaction.fetchOne( - From(TestEntity2), + From("Config2"), Where("testEntityID", isEqualTo: 4), Tweak { (fetchRequest) -> Void in @@ -239,7 +239,7 @@ class HardcoreDataTests: XCTestCase { let detachedExpectation = self.expectationWithDescription("Query creation") - let obj5 = detachedTransaction.create(TestEntity1) + let obj5 = detachedTransaction.create(Into("Config1")) obj5.testEntityID = 5 obj5.testString = "hihihi" obj5.testNumber = 70 @@ -259,7 +259,7 @@ class HardcoreDataTests: XCTestCase { ) XCTAssertTrue(count == 1, "count == 1 (actual: \(count))") - let obj6 = detachedTransaction.create(TestEntity1) + let obj6 = detachedTransaction.create(Into()) obj6.testEntityID = 6 obj6.testString = "huehuehue" obj6.testNumber = 130 diff --git a/HardcoreDataTests/Model.xcdatamodeld/Model.xcdatamodel/contents b/HardcoreDataTests/Model.xcdatamodeld/Model.xcdatamodel/contents index cdfa223..c8d89a9 100644 --- a/HardcoreDataTests/Model.xcdatamodeld/Model.xcdatamodel/contents +++ b/HardcoreDataTests/Model.xcdatamodeld/Model.xcdatamodel/contents @@ -1,5 +1,5 @@ - + diff --git a/Libraries/GCDKit b/Libraries/GCDKit index a11a07a..e99b029 160000 --- a/Libraries/GCDKit +++ b/Libraries/GCDKit @@ -1 +1 @@ -Subproject commit a11a07aa2585c5c294d1547e82829b402ad59dc3 +Subproject commit e99b0298d6fd32b64425a1058f6c04f67f0390f8 diff --git a/README.md b/README.md index 2c96ad8..169c9aa 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ [![License](https://img.shields.io/cocoapods/l/HardcoreData.svg?style=flat)](http://cocoadocs.org/docsets/HardcoreData) Simple, elegant, and smart Core Data programming with Swift +(Swift only, iOS 8+ only) + + ## Features - Supports multiple persistent stores per *data stack*, just the way .xcdatamodeld files are supposed to. HardcoreData will also manage one *data stack* by default, but you can create and manage as many as you need. @@ -18,7 +21,7 @@ Simple, elegant, and smart Core Data programming with Swift Quick-setup: ```swift -HardcoreData.defaultStack.addSQLiteStore("MyStore.sqlite") +HardcoreData.addSQLiteStore("MyStore.sqlite") ``` Simple transactions: @@ -61,8 +64,19 @@ let count = HardcoreData.queryValue( ``` +## Quick jumps -## Architecture +- [Architecture](#architecture) +- [Setting up](#setup) +- [Saving and processing transactions](#transactions) +- [Fetching and querying](#fetch_query) +- [Logging and error handling](#logging) +- [Observing changes and notifications](#observing) +- [Importing data](#importing) + + + +## Architecture For maximum safety and performance, HardcoreData will enforce coding patterns and practices it was designed for. (Don't worry, it's not as scary as it sounds.) But it is advisable to understand the "magic" of HardcoreData before you use it in your apps. If you are already familiar with the inner workings of CoreData, here is a mapping of `HardcoreData` abstractions: @@ -73,7 +87,7 @@ If you are already familiar with the inner workings of CoreData, here is a mappi | `NSPersistentStore`
("Configuration"s in the .xcdatamodeld file) | `DataStack` configuration
(multiple sqlite / in-memory stores per stack) | | `NSManagedObjectContext` | `BaseDataTransaction` subclasses
(`SynchronousDataTransaction`, `AsynchronousDataTransaction`, `DetachedDataTransaction`) | -RestKit and MagicalRecord set up their `NSManagedObjectContext`s this way: +Popular libraries [RestKit](https://github.com/RestKit/RestKit) and [MagicalRecord](https://github.com/magicalpanda/MagicalRecord) set up their `NSManagedObjectContext`s this way: nested contexts @@ -85,7 +99,7 @@ This allows for a butter-smooth main thread, while still taking advantage of saf -## Setting up +## Setting up The simplest way to initialize HardcoreData is to add a default store to the default stack: ```swift HardcoreData.defaultStack.addSQLiteStore() @@ -154,23 +168,27 @@ Check out the *HardcoreData.swift* and *DataStack.swift files* if you want to ex -## Saving and processing transactions +## Saving and processing transactions (implemented; README pending) -## Fetching and querying + +## Fetching and querying (implemented; README pending) -## Logging and error handling + +## Logging and error handling (implemented; README pending) -## Observing changes and notifications + +## Observing changes and notifications (implemented; README pending) -## Importing data + +## Importing data (currently implementing)