From 59ad525786115cefd5597aeded7ac4bb246f826d Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sat, 8 Aug 2015 10:24:40 +0900 Subject: [PATCH 01/21] fixed for Xcode 7 beta 5 --- CoreStore/Logging/DefaultLogger.swift | 4 ++-- CoreStore/Observing/ListMonitor.swift | 2 +- CoreStore/Observing/ObjectMonitor.swift | 2 +- .../Loggers Demo/CustomLoggerViewController.swift | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CoreStore/Logging/DefaultLogger.swift b/CoreStore/Logging/DefaultLogger.swift index b604ade..1ebf61a 100644 --- a/CoreStore/Logging/DefaultLogger.swift +++ b/CoreStore/Logging/DefaultLogger.swift @@ -62,14 +62,14 @@ public final class DefaultLogger: CoreStoreLogger { icon = "❗" levelString = "Fatal" } - Swift.print("\(icon) [CoreStore: \(levelString)] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n") + Swift.print("\(icon) [CoreStore: \(levelString)] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n") #endif } public func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { #if DEBUG - Swift.print("⚠️ [CoreStore: Error] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n \(error)\n") + Swift.print("⚠️ [CoreStore: Error] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n \(error)\n") #endif } diff --git a/CoreStore/Observing/ListMonitor.swift b/CoreStore/Observing/ListMonitor.swift index a7e93bb..f4aa4ad 100644 --- a/CoreStore/Observing/ListMonitor.swift +++ b/CoreStore/Observing/ListMonitor.swift @@ -723,7 +723,7 @@ private final class FetchedResultsControllerDelegate: NSObject, NSFetchedResults self.handler?.controllerDidChangeContent(controller) } - @objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: NSManagedObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { + @objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath) } diff --git a/CoreStore/Observing/ObjectMonitor.swift b/CoreStore/Observing/ObjectMonitor.swift index cf266fe..fb1d729 100644 --- a/CoreStore/Observing/ObjectMonitor.swift +++ b/CoreStore/Observing/ObjectMonitor.swift @@ -321,7 +321,7 @@ private final class FetchedResultsControllerDelegate: NSObject, NSFetchedResults self.handler?.controllerWillChangeContent(controller) } - @objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: NSManagedObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { + @objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath) } diff --git a/CoreStoreDemo/CoreStoreDemo/Loggers Demo/CustomLoggerViewController.swift b/CoreStoreDemo/CoreStoreDemo/Loggers Demo/CustomLoggerViewController.swift index 333a4a2..eea35a3 100644 --- a/CoreStoreDemo/CoreStoreDemo/Loggers Demo/CustomLoggerViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/Loggers Demo/CustomLoggerViewController.swift @@ -62,7 +62,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger { case .Warning: levelString = "Warning" case .Fatal: levelString = "Fatal" } - self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n") + self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n") } } @@ -70,7 +70,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger { GCDQueue.Main.async { [weak self] in - self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n") + self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n") } } @@ -83,13 +83,13 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger { GCDQueue.Main.async { [weak self] in - self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n") + self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n") } } @noreturn func fatalError(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { - Swift.fatalError("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Abort] \(message)") + Swift.fatalError("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Abort] \(message)") } From 83c724f58447849bd7507d45dc7fae36e7ca8dce Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sat, 8 Aug 2015 22:12:08 +0900 Subject: [PATCH 02/21] updated default logger to still run assertions even on optimized (-O) builds --- CoreStore.podspec | 2 +- CoreStore/Info.plist | 2 +- CoreStore/Logging/DefaultLogger.swift | 7 +++++- .../DetachedDataTransaction.swift | 24 +++++++++---------- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/CoreStore.podspec b/CoreStore.podspec index 3bda7e8..e8e2abf 100644 --- a/CoreStore.podspec +++ b/CoreStore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CoreStore" - s.version = "1.0.2" + s.version = "1.1.2" s.license = "MIT" s.summary = "Simple, elegant, and smart Core Data programming with Swift" s.homepage = "https://github.com/JohnEstropia/CoreStore" diff --git a/CoreStore/Info.plist b/CoreStore/Info.plist index 9645e5d..3034b22 100644 --- a/CoreStore/Info.plist +++ b/CoreStore/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0.2 + 1.1.2 CFBundleSignature ???? CFBundleVersion diff --git a/CoreStore/Logging/DefaultLogger.swift b/CoreStore/Logging/DefaultLogger.swift index 1ebf61a..7df9c98 100644 --- a/CoreStore/Logging/DefaultLogger.swift +++ b/CoreStore/Logging/DefaultLogger.swift @@ -76,7 +76,12 @@ public final class DefaultLogger: CoreStoreLogger { public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { #if DEBUG - Swift.assert(condition, "❗ [CoreStore: Assertion Failure] \(message)", file: fileName, line: numericCast(lineNumber)) + if condition() { + + return + } + Swift.print("❗ [CoreStore: Assertion Failure] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n") + Swift.fatalError() #endif } } diff --git a/CoreStore/Saving and Processing/DetachedDataTransaction.swift b/CoreStore/Saving and Processing/DetachedDataTransaction.swift index d636b7a..1cf7dd0 100644 --- a/CoreStore/Saving and Processing/DetachedDataTransaction.swift +++ b/CoreStore/Saving and Processing/DetachedDataTransaction.swift @@ -36,18 +36,6 @@ public final class DetachedDataTransaction: BaseDataTransaction { // MARK: Public - /** - Returns the `NSManagedObjectContext` for this detached transaction. Use only for cases where external frameworks need an `NSManagedObjectContext` instance to work with. - - Note that it is the developer's responsibility to ensure the following: - - that the `DetachedDataTransaction` that owns this context should be strongly referenced and prevented from being deallocated during the context's lifetime - - that all saves will be done either through the `DetachedDataTransaction`'s `commit(...)` method, or by calling `save()` manually on the context, its parent, and all other ancestor contexts if there are any. - */ - public var internalContext: NSManagedObjectContext { - - return self.context - } - /** Saves the transaction changes asynchronously. For a `DetachedDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread. @@ -75,6 +63,18 @@ public final class DetachedDataTransaction: BaseDataTransaction { ) } + /** + Returns the `NSManagedObjectContext` for this detached transaction. Use only for cases where external frameworks need an `NSManagedObjectContext` instance to work with. + + Note that it is the developer's responsibility to ensure the following: + - that the `DetachedDataTransaction` that owns this context should be strongly referenced and prevented from being deallocated during the context's lifetime + - that all saves will be done either through the `DetachedDataTransaction`'s `commit(...)` method, or by calling `save()` manually on the context, its parent, and all other ancestor contexts if there are any. + */ + public var internalContext: NSManagedObjectContext { + + return self.context + } + // MARK: Internal From 283104af3f31bc7012b18799a0e43cf49ae066a0 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sun, 9 Aug 2015 05:04:47 +0900 Subject: [PATCH 03/21] thanks to protocol extensions, you can now omit ListObserver and ObjectObserver methods you don't need to implement --- CoreStore/Observing/ListObserver.swift | 49 +++++++++++++++++++ CoreStore/Observing/ObjectObserver.swift | 19 +++++++ .../ObjectObserverDemoViewController.swift | 5 -- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/CoreStore/Observing/ListObserver.swift b/CoreStore/Observing/ListObserver.swift index 7a9581a..ccb18a7 100644 --- a/CoreStore/Observing/ListObserver.swift +++ b/CoreStore/Observing/ListObserver.swift @@ -60,6 +60,19 @@ public protocol ListObserver: class { func listMonitorDidChange(monitor: ListMonitor) } +public extension ListObserver { + + /** + The default implementation does nothing. + */ + func listMonitorWillChange(monitor: ListMonitor) { } + + /** + The default implementation does nothing. + */ + func listMonitorDidChange(monitor: ListMonitor) { } +} + // MARK: - ListObjectObserver @@ -112,6 +125,29 @@ public protocol ListObjectObserver: ListObserver { func listMonitor(monitor: ListMonitor, didMoveObject object: EntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) } +public extension ListObjectObserver { + + /** + The default implementation does nothing. + */ + func listMonitor(monitor: ListMonitor, didInsertObject object: EntityType, toIndexPath indexPath: NSIndexPath) { } + + /** + The default implementation does nothing. + */ + func listMonitor(monitor: ListMonitor, didDeleteObject object: EntityType, fromIndexPath indexPath: NSIndexPath) { } + + /** + The default implementation does nothing. + */ + func listMonitor(monitor: ListMonitor, didUpdateObject object: EntityType, atIndexPath indexPath: NSIndexPath) { } + + /** + The default implementation does nothing. + */ + func listMonitor(monitor: ListMonitor, didMoveObject object: EntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { } +} + // MARK: - ListSectionObserver @@ -145,3 +181,16 @@ public protocol ListSectionObserver: ListObjectObserver { */ func listMonitor(monitor: ListMonitor, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) } + +public extension ListSectionObserver { + + /** + The default implementation does nothing. + */ + func listMonitor(monitor: ListMonitor, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { } + + /** + The default implementation does nothing. + */ + func listMonitor(monitor: ListMonitor, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { } +} diff --git a/CoreStore/Observing/ObjectObserver.swift b/CoreStore/Observing/ObjectObserver.swift index 40bcbc6..65118ff 100644 --- a/CoreStore/Observing/ObjectObserver.swift +++ b/CoreStore/Observing/ObjectObserver.swift @@ -67,3 +67,22 @@ public protocol ObjectObserver: class { */ func objectMonitor(monitor: ObjectMonitor, didDeleteObject object: EntityType) } + + +public extension ObjectObserver { + + /** + The default implementation does nothing. + */ + func objectMonitor(monitor: ObjectMonitor, willUpdateObject object: EntityType) { } + + /** + The default implementation does nothing. + */ + func objectMonitor(monitor: ObjectMonitor, didUpdateObject object: EntityType, changedPersistentKeys: Set) { } + + /** + The default implementation does nothing. + */ + func objectMonitor(monitor: ObjectMonitor, didDeleteObject object: EntityType) { } +} diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift index 2942f00..1a004aa 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift @@ -80,11 +80,6 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver { // MARK: ObjectObserver - func objectMonitor(monitor: ObjectMonitor, willUpdateObject object: Palette) { - - // none - } - func objectMonitor(monitor: ObjectMonitor, didUpdateObject object: Palette, changedPersistentKeys: Set) { self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys) From 8066bf2a5ac0fb056cd36f1003798f11399d2329 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sun, 9 Aug 2015 05:05:26 +0900 Subject: [PATCH 04/21] fixed assertion failures when fetching from detached data transactions --- .../BaseDataTransaction+Querying.swift | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/CoreStore/Fetching and Querying/BaseDataTransaction+Querying.swift b/CoreStore/Fetching and Querying/BaseDataTransaction+Querying.swift index c7db64a..861b805 100644 --- a/CoreStore/Fetching and Querying/BaseDataTransaction+Querying.swift +++ b/CoreStore/Fetching and Querying/BaseDataTransaction+Querying.swift @@ -43,7 +43,7 @@ public extension BaseDataTransaction { public func fetchOne(from: From, _ fetchClauses: FetchClause...) -> T? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue." ) @@ -60,7 +60,7 @@ public extension BaseDataTransaction { public func fetchOne(from: From, _ fetchClauses: [FetchClause]) -> T? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue." ) @@ -77,7 +77,7 @@ public extension BaseDataTransaction { public func fetchAll(from: From, _ fetchClauses: FetchClause...) -> [T]? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue." ) @@ -94,7 +94,7 @@ public extension BaseDataTransaction { public func fetchAll(from: From, _ fetchClauses: [FetchClause]) -> [T]? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue." ) @@ -111,7 +111,7 @@ public extension BaseDataTransaction { public func fetchCount(from: From, _ fetchClauses: FetchClause...) -> Int? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue." ) @@ -128,7 +128,7 @@ public extension BaseDataTransaction { public func fetchCount(from: From, _ fetchClauses: [FetchClause]) -> Int? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue." ) @@ -145,7 +145,7 @@ public extension BaseDataTransaction { public func fetchObjectID(from: From, _ fetchClauses: FetchClause...) -> NSManagedObjectID? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue." ) @@ -162,7 +162,7 @@ public extension BaseDataTransaction { public func fetchObjectID(from: From, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue." ) @@ -179,7 +179,7 @@ public extension BaseDataTransaction { public func fetchObjectIDs(from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue." ) @@ -196,7 +196,7 @@ public extension BaseDataTransaction { public func fetchObjectIDs(from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue." ) @@ -213,7 +213,7 @@ public extension BaseDataTransaction { public func deleteAll(from: From, _ deleteClauses: DeleteClause...) -> Int? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete from a \(typeName(self)) outside its designated queue." ) @@ -230,7 +230,7 @@ public extension BaseDataTransaction { public func deleteAll(from: From, _ deleteClauses: [DeleteClause]) -> Int? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete from a \(typeName(self)) outside its designated queue." ) @@ -250,7 +250,7 @@ public extension BaseDataTransaction { public func queryValue(from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> U? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside its designated queue." ) @@ -270,7 +270,7 @@ public extension BaseDataTransaction { public func queryValue(from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> U? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside its designated queue." ) @@ -290,7 +290,7 @@ public extension BaseDataTransaction { public func queryAttributes(from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside its designated queue." ) @@ -310,7 +310,7 @@ public extension BaseDataTransaction { public func queryAttributes(from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside its designated queue." ) From 64aa97264e28685005abc325d8a8e3709b06ef53 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sun, 9 Aug 2015 05:06:42 +0900 Subject: [PATCH 05/21] added utilities for importing data --- CoreStore.xcodeproj/project.pbxproj | 16 ++ .../BaseDataTransaction+Importing.swift | 245 ++++++++++++++++++ CoreStore/Internal/Functions.swift | 47 ++++ 3 files changed, 308 insertions(+) create mode 100644 CoreStore/Importing Data/BaseDataTransaction+Importing.swift create mode 100644 CoreStore/Internal/Functions.swift diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 8328e5b..b161fae 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ B5D39A0419FD00DE000E91BB /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0319FD00DE000E91BB /* UIKit.framework */; }; B5D5E0CF1A4D6AAB006468AF /* TestEntity2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D5E0CE1A4D6AAB006468AF /* TestEntity2.swift */; }; B5D8080E1A3471A500A44484 /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D808021A34715700A44484 /* GCDKit.framework */; }; + B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */; }; + B5E834BB1B7691F3001D3D50 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834BA1B7691F3001D3D50 /* Functions.swift */; }; B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; }; B5E84EE11AFF84500064E85B /* PersistentStoreResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDE1AFF84500064E85B /* PersistentStoreResult.swift */; }; B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE31AFF84610064E85B /* DefaultLogger.swift */; }; @@ -126,6 +128,8 @@ B5D5E0CE1A4D6AAB006468AF /* TestEntity2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity2.swift; sourceTree = ""; }; B5D806C51A34715700A44484 /* GCDKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = GCDKit.xcodeproj; sourceTree = ""; }; B5D9C8F61B160ED200E64F0E /* CoreStore.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = CoreStore.podspec; sourceTree = SOURCE_ROOT; }; + B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseDataTransaction+Importing.swift"; sourceTree = ""; }; + B5E834BA1B7691F3001D3D50 /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; B5E84ED81AFF82360064E85B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; B5E84ED91AFF82360064E85B /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = SOURCE_ROOT; }; B5E84EDB1AFF84500064E85B /* DataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataStack.swift; sourceTree = ""; }; @@ -221,6 +225,7 @@ B5E84EDA1AFF84500064E85B /* Setting Up */, B5E84EE21AFF84610064E85B /* Logging */, B5E84EE91AFF846E0064E85B /* Saving and Processing */, + B5E834B61B7630BD001D3D50 /* Importing Data */, B5E84EFD1AFF847B0064E85B /* Fetching and Querying */, B5E84F191AFF84860064E85B /* Observing */, B56964D11B22FF700075EE4A /* Migrating */, @@ -309,6 +314,14 @@ name = Products; sourceTree = ""; }; + B5E834B61B7630BD001D3D50 /* Importing Data */ = { + isa = PBXGroup; + children = ( + B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */, + ); + path = "Importing Data"; + sourceTree = ""; + }; B5E84EDA1AFF84500064E85B /* Setting Up */ = { isa = PBXGroup; children = ( @@ -414,6 +427,7 @@ B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */, B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */, B5E84F2D1AFF849C0064E85B /* WeakObject.swift */, + B5E834BA1B7691F3001D3D50 /* Functions.swift */, ); path = Internal; sourceTree = ""; @@ -578,6 +592,7 @@ B5FAD6AC1B51285300714891 /* MigrationManager.swift in Sources */, B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */, B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */, + B5E834BB1B7691F3001D3D50 /* Functions.swift in Sources */, B5E84F231AFF84860064E85B /* ListMonitor.swift in Sources */, B5E84EF71AFF846E0064E85B /* DetachedDataTransaction.swift in Sources */, B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */, @@ -597,6 +612,7 @@ B5E84EF81AFF846E0064E85B /* CoreStore+Transaction.swift in Sources */, B5E84F301AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift in Sources */, B5E84F211AFF84860064E85B /* CoreStore+Observing.swift in Sources */, + B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */, B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */, B5E84EF41AFF846E0064E85B /* AsynchronousDataTransaction.swift in Sources */, B5E84F151AFF847B0064E85B /* CoreStore+Querying.swift in Sources */, diff --git a/CoreStore/Importing Data/BaseDataTransaction+Importing.swift b/CoreStore/Importing Data/BaseDataTransaction+Importing.swift new file mode 100644 index 0000000..58d8be2 --- /dev/null +++ b/CoreStore/Importing Data/BaseDataTransaction+Importing.swift @@ -0,0 +1,245 @@ +// +// BaseDataTransaction+Importing.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 + + +public protocol ImportableObject: class { + + typealias ImportSource + + static func shouldImportFromSource(source: ImportSource) -> Bool + + func didInsertFromImportSource(source: ImportSource) throws + + func updateFromImportSource(source: ImportSource) throws +} + +public extension ImportableObject { + + static func shouldImportFromSource(source: ImportSource) -> Bool { + + return true + } +} + + +public protocol ImportableUniqueObject: ImportableObject { + + typealias UniqueIDType: NSObject + + static var uniqueIDKeyPath: String { get } + + var uniqueIDValue: UniqueIDType { get set } + + static func uniqueIDFromImportSource(source: ImportSource) throws -> (key: String, value: UniqueIDType!) +} + + +public extension BaseDataTransaction { + + func importObject( + into: Into, + source: T.ImportSource) throws -> T? { + + CoreStore.assert( + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), + "Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue." + ) + + var returnValue: T? + try autoreleasepool { + + if !T.shouldImportFromSource(source) { + + return + } + + let object = self.create(into) + try object.didInsertFromImportSource(source) + returnValue = object + } + return returnValue + } + + func importUniqueObject( + into: Into, + source: T.ImportSource) throws -> T? { + + CoreStore.assert( + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), + "Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue." + ) + + var returnValue: T? + try autoreleasepool { + + if !T.shouldImportFromSource(source) { + + return + } + + let uniqueIDKeyPath = T.uniqueIDKeyPath + let uniqueIDFromImportSource = try T.uniqueIDFromImportSource(source) + let uniqueIDValue = uniqueIDFromImportSource.value + + if let object = self.fetchOne(From(T), Where(uniqueIDKeyPath, isEqualTo: uniqueIDValue)) { + + try object.updateFromImportSource(source) + returnValue = object + } + else { + + let object = self.create(into) + object.uniqueIDValue = uniqueIDValue + try object.didInsertFromImportSource(source) + returnValue = object + } + } + return returnValue + } + + func importUniqueObjects( + into: Into, + sourceArray: [T.ImportSource], + preProcess: ((mapping: [T.UniqueIDType: T.ImportSource]) throws -> Void)? = nil) throws { + + CoreStore.assert( + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), + "Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue." + ) + + try autoreleasepool { + + var mapping = Dictionary() + for source in sourceArray { + + try autoreleasepool { + + if !T.shouldImportFromSource(source) { + + return + } + + let uniqueIDValue = try T.uniqueIDFromImportSource(source).value + mapping[uniqueIDValue] = source + } + } + + if let preProcess = preProcess { + + try autoreleasepool { + + try preProcess(mapping: mapping) + } + } + + for object in self.fetchAll(From(T), Where("%K IN %@", T.uniqueIDKeyPath, mapping.keys.array)) ?? [] { + + try autoreleasepool { + + let uniqueIDValue = object.uniqueIDValue + try object.updateFromImportSource(mapping.removeValueForKey(uniqueIDValue)!) + } + } + + for (uniqueIDValue, source) in mapping { + + try autoreleasepool { + + let object = self.create(into) + object.uniqueIDValue = uniqueIDValue + try object.didInsertFromImportSource(source) + } + } + } + } + + func importUniqueObjects( + into: Into, + sourceArray: [T.ImportSource], + preProcess: ((mapping: [T.UniqueIDType: T.ImportSource]) throws -> Void)? = nil, + postProcess: (sorted: [T]) -> Void) throws { + + CoreStore.assert( + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), + "Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue." + ) + + try autoreleasepool { + + var sortedIDs = Array() + var mapping = Dictionary() + for source in sourceArray { + + try autoreleasepool { + + if !T.shouldImportFromSource(source) { + + return + } + + let uniqueIDValue = try T.uniqueIDFromImportSource(source).value + mapping[uniqueIDValue] = source + sortedIDs.append(uniqueIDValue) + } + } + + if let preProcess = preProcess { + + try autoreleasepool { + + try preProcess(mapping: mapping) + } + } + + var objects = Dictionary() + for object in self.fetchAll(From(T), Where("%K IN %@", T.uniqueIDKeyPath, mapping.keys.array)) ?? [] { + + try autoreleasepool { + + let uniqueIDValue = object.uniqueIDValue + try object.updateFromImportSource(mapping.removeValueForKey(uniqueIDValue)!) + objects[uniqueIDValue] = object + } + } + + for (uniqueIDValue, source) in mapping { + + try autoreleasepool { + + let object = self.create(into) + object.uniqueIDValue = uniqueIDValue + try object.didInsertFromImportSource(source) + + objects[uniqueIDValue] = object + } + } + + postProcess(sorted: sortedIDs.flatMap { objects[$0] }) + } + } +} diff --git a/CoreStore/Internal/Functions.swift b/CoreStore/Internal/Functions.swift new file mode 100644 index 0000000..c221e2e --- /dev/null +++ b/CoreStore/Internal/Functions.swift @@ -0,0 +1,47 @@ +// +// Functions.swift +// CoreStore +// +// Copyright (c) 2014 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 + +internal func autoreleasepool(@noescape closure: () throws -> Void) throws { + + var closureError: ErrorType? + ObjectiveC.autoreleasepool { + + do { + + try closure() + } + catch { + + closureError = error + } + } + + if let closureError = closureError { + + throw closureError + } +} From 2fb3263aa18fe7812ccd3a38c4d7d5377c2f3998 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sun, 9 Aug 2015 18:27:21 +0900 Subject: [PATCH 06/21] refactored autoreleasepool calls --- .../BaseDataTransaction+Importing.swift | 18 ++++------ CoreStore/Internal/Functions.swift | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/CoreStore/Importing Data/BaseDataTransaction+Importing.swift b/CoreStore/Importing Data/BaseDataTransaction+Importing.swift index 58d8be2..c8e7326 100644 --- a/CoreStore/Importing Data/BaseDataTransaction+Importing.swift +++ b/CoreStore/Importing Data/BaseDataTransaction+Importing.swift @@ -70,19 +70,17 @@ public extension BaseDataTransaction { "Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue." ) - var returnValue: T? - try autoreleasepool { + return try autoreleasepool { if !T.shouldImportFromSource(source) { - return + return nil } let object = self.create(into) try object.didInsertFromImportSource(source) - returnValue = object + return object } - return returnValue } func importUniqueObject( @@ -94,12 +92,11 @@ public extension BaseDataTransaction { "Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue." ) - var returnValue: T? - try autoreleasepool { + return try autoreleasepool { if !T.shouldImportFromSource(source) { - return + return nil } let uniqueIDKeyPath = T.uniqueIDKeyPath @@ -109,17 +106,16 @@ public extension BaseDataTransaction { if let object = self.fetchOne(From(T), Where(uniqueIDKeyPath, isEqualTo: uniqueIDValue)) { try object.updateFromImportSource(source) - returnValue = object + return object } else { let object = self.create(into) object.uniqueIDValue = uniqueIDValue try object.didInsertFromImportSource(source) - returnValue = object + return object } } - return returnValue } func importUniqueObjects( diff --git a/CoreStore/Internal/Functions.swift b/CoreStore/Internal/Functions.swift index c221e2e..6fbba1d 100644 --- a/CoreStore/Internal/Functions.swift +++ b/CoreStore/Internal/Functions.swift @@ -25,6 +25,40 @@ import Foundation +internal func autoreleasepool(@noescape closure: () -> T?) -> T? { + + var closureValue: T? + ObjectiveC.autoreleasepool { + + closureValue = closure() + } + + return closureValue +} + +internal func autoreleasepool(@noescape closure: () throws -> T?) throws -> T? { + + var closureValue: T? + var closureError: ErrorType? + ObjectiveC.autoreleasepool { + + do { + + closureValue = try closure() + } + catch { + + closureError = error + } + } + + if let closureError = closureError { + + throw closureError + } + return closureValue +} + internal func autoreleasepool(@noescape closure: () throws -> Void) throws { var closureError: ErrorType? From cbc3eb888737b43a7ad6cee092de8ac2f79e3be0 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 11 Aug 2015 15:37:44 +0800 Subject: [PATCH 07/21] woops, develop branch should be Swift 2.0, not 1.2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f11ee1c..ded62d4 100644 --- a/README.md +++ b/README.md @@ -956,7 +956,7 @@ let person2 = self.monitor[1, 2] # Installation - Requires: - iOS 8 SDK and above - - Swift 1.2 + - Swift 2.0 - Dependencies: - [GCDKit](https://github.com/JohnEstropia/GCDKit) From a263851266a2640a0033c5590fb62388d25bd710 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Mon, 17 Aug 2015 23:50:03 +0900 Subject: [PATCH 08/21] import array of ImportableObjects --- .../BaseDataTransaction+Importing.swift | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/CoreStore/Importing Data/BaseDataTransaction+Importing.swift b/CoreStore/Importing Data/BaseDataTransaction+Importing.swift index c8e7326..e5d2b93 100644 --- a/CoreStore/Importing Data/BaseDataTransaction+Importing.swift +++ b/CoreStore/Importing Data/BaseDataTransaction+Importing.swift @@ -83,6 +83,55 @@ public extension BaseDataTransaction { } } + func importObjects( + into: Into, + sourceArray: [T.ImportSource]) throws { + + CoreStore.assert( + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), + "Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue." + ) + + try autoreleasepool { + + for source in sourceArray { + + try autoreleasepool { + + let object = self.create(into) + try object.didInsertFromImportSource(source) + } + } + } + } + + func importObjects( + into: Into, + sourceArray: [T.ImportSource], + postProcess: (sorted: [T]) -> Void) throws { + + CoreStore.assert( + self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(), + "Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue." + ) + + try autoreleasepool { + + var objects = [T]() + for source in sourceArray { + + try autoreleasepool { + + let object = self.create(into) + try object.didInsertFromImportSource(source) + + objects.append(object) + } + } + postProcess(sorted: objects) + } + } + func importUniqueObject( into: Into, source: T.ImportSource) throws -> T? { From 8c6a7df731c2452f80b296fc11297e756d1d5c18 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 18 Aug 2015 21:40:33 +0900 Subject: [PATCH 09/21] provide a way to enumerate entities managed by the DataStack --- CoreStore/Internal/NSManagedObjectModel+Setup.swift | 9 +++++++++ CoreStore/Setting Up/DataStack.swift | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/CoreStore/Internal/NSManagedObjectModel+Setup.swift b/CoreStore/Internal/NSManagedObjectModel+Setup.swift index 20f93c1..8ddd4b5 100644 --- a/CoreStore/Internal/NSManagedObjectModel+Setup.swift +++ b/CoreStore/Internal/NSManagedObjectModel+Setup.swift @@ -150,6 +150,15 @@ internal extension NSManagedObjectModel { return self.entityNameMapping[NSStringFromClass(entityClass)]! } + @nonobjc internal func entityMapping() -> [String: NSManagedObject.Type] { + + return self.entityNameMapping.reduce([:]) { (var mapping, pair) in + + mapping[pair.0] = (NSClassFromString(pair.1)! as! NSManagedObject.Type) + return mapping + } + } + @nonobjc internal func mergedModels() -> [NSManagedObjectModel] { return self.modelVersions?.map { self[$0] }.flatMap { $0 == nil ? [] : [$0!] } ?? [self] diff --git a/CoreStore/Setting Up/DataStack.swift b/CoreStore/Setting Up/DataStack.swift index dbb9c10..8c3bdf6 100644 --- a/CoreStore/Setting Up/DataStack.swift +++ b/CoreStore/Setting Up/DataStack.swift @@ -81,6 +81,14 @@ public final class DataStack { return self.model.currentModelVersion! } + /** + Returns the entity name-to-class type mapping from the `DataStack`'s model. + */ + public var entitiesByName: [String: NSManagedObject.Type] { + + return self.model.entityMapping() + } + /** Adds an in-memory store to the stack. From 62b11309f3ba37e649e9cc371a1901c26a1e68bb Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 18 Aug 2015 21:49:40 +0900 Subject: [PATCH 10/21] expose DataStack vars to CoreStore --- CoreStore/Setting Up/CoreStore+Setup.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CoreStore/Setting Up/CoreStore+Setup.swift b/CoreStore/Setting Up/CoreStore+Setup.swift index bd81e0b..11b42cf 100644 --- a/CoreStore/Setting Up/CoreStore+Setup.swift +++ b/CoreStore/Setting Up/CoreStore+Setup.swift @@ -32,6 +32,22 @@ import GCDKit public extension CoreStore { + /** + Returns the `defaultStack`'s model version. The version string is the same as the name of the version-specific .xcdatamodeld file. + */ + public static var modelVersion: String { + + return self.defaultStack.modelVersion + } + + /** + Returns the entity name-to-class type mapping from the `defaultStack`'s model. + */ + public static var entitiesByName: [String: NSManagedObject.Type] { + + return self.defaultStack.entitiesByName + } + /** Adds an in-memory store to the `defaultStack`. From 67ccae4ef684e09df62c8f1ebc803b4df8c7f8eb Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 19 Aug 2015 11:21:39 +0900 Subject: [PATCH 11/21] added missing parameters --- CoreStore/Migrating/CoreStore+Migration.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CoreStore/Migrating/CoreStore+Migration.swift b/CoreStore/Migrating/CoreStore+Migration.swift index c9f98f7..a994673 100644 --- a/CoreStore/Migrating/CoreStore+Migration.swift +++ b/CoreStore/Migrating/CoreStore+Migration.swift @@ -38,15 +38,17 @@ public extension CoreStore { - parameter 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. - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. - parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`. + - parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false. - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously. - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required */ - public static func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? { + public static func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? { return try self.defaultStack.addSQLiteStore( fileName: fileName, configuration: configuration, mappingModelBundles: mappingModelBundles, + resetStoreOnModelMismatch: resetStoreOnModelMismatch, completion: completion ) } @@ -57,15 +59,17 @@ public extension CoreStore { - parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a ".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. - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. - parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`. + - parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false. - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously. - returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required */ - public static func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), completion: (PersistentStoreResult) -> Void) throws -> NSProgress? { + public static func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? { return try self.defaultStack.addSQLiteStore( fileURL: fileURL, configuration: configuration, mappingModelBundles: mappingModelBundles, + resetStoreOnModelMismatch: resetStoreOnModelMismatch, completion: completion ) } From b92ee76907a0d6a9c7969a4a11a69779e94f1522 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 19 Aug 2015 12:19:14 +0900 Subject: [PATCH 12/21] added utility to refresh an object --- .../NSManagedObject+Convenience.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CoreStore/Convenience Helpers/NSManagedObject+Convenience.swift b/CoreStore/Convenience Helpers/NSManagedObject+Convenience.swift index 989e85c..fe33339 100644 --- a/CoreStore/Convenience Helpers/NSManagedObject+Convenience.swift +++ b/CoreStore/Convenience Helpers/NSManagedObject+Convenience.swift @@ -58,4 +58,20 @@ public extension NSManagedObject { self.setPrimitiveValue(value, forKey: KVCKey) self.didChangeValueForKey(KVCKey) } + + /** + Re-faults the object to use the latest values from the persistent store + */ + public func refreshAsFault() { + + self.managedObjectContext?.refreshObject(self, mergeChanges: false) + } + + /** + Re-faults the object to use the latest values from the persistent store and merges previously pending changes back + */ + public func refreshAndMerge() { + + self.managedObjectContext?.refreshObject(self, mergeChanges: true) + } } From 5a96ef13f66856a19d0d7d07b28c43a27b4d32cb Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 19 Aug 2015 15:07:15 +0900 Subject: [PATCH 13/21] added utility to get NSManagedObjectID from an object URI --- CoreStore/Setting Up/DataStack.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CoreStore/Setting Up/DataStack.swift b/CoreStore/Setting Up/DataStack.swift index 8c3bdf6..e435c0a 100644 --- a/CoreStore/Setting Up/DataStack.swift +++ b/CoreStore/Setting Up/DataStack.swift @@ -89,6 +89,14 @@ public final class DataStack { return self.model.entityMapping() } + /** + Returns the `NSManagedObjectID` for the specified object URI if it exists in the persistent store. + */ + public func objectIDForURIRepresentation(url: NSURL) -> NSManagedObjectID? { + + return self.coordinator.managedObjectIDForURIRepresentation(url) + } + /** Adds an in-memory store to the stack. From a29a4b38fec670c8297a13bdddc5da92d7eaa08e Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 19 Aug 2015 15:53:20 +0900 Subject: [PATCH 14/21] added utilities to get existing NSManagedObject instances using object IDs --- .../CoreStore+Querying.swift | 44 +++++++++++ .../DataStack+Querying.swift | 78 +++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/CoreStore/Fetching and Querying/CoreStore+Querying.swift b/CoreStore/Fetching and Querying/CoreStore+Querying.swift index 1d28e95..c44db1b 100644 --- a/CoreStore/Fetching and Querying/CoreStore+Querying.swift +++ b/CoreStore/Fetching and Querying/CoreStore+Querying.swift @@ -31,6 +31,50 @@ public extension CoreStore { // MARK: Public + /** + Using the `defaultStack`, fetches the `NSManagedObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context. + + - parameter object: a reference to the object created/fetched outside the `DataStack` + - returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found. + */ + public static func fetchExisting(object: T) -> T? { + + return self.defaultStack.fetchExisting(object) + } + + /** + Using the `defaultStack`, fetches the `NSManagedObject` instance in the `DataStack`'s context from an `NSManagedObjectID`. + + - parameter objectID: the `NSManagedObjectID` for the object + - returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found. + */ + public static func fetchExisting(objectID: NSManagedObjectID) -> T? { + + return self.defaultStack.fetchExisting(objectID) + } + + /** + Using the `defaultStack`, fetches the `NSManagedObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context. + + - parameter objects: an array of `NSManagedObject`s created/fetched outside the `DataStack` + - returns: the `NSManagedObject` array for objects that exists in the `DataStack` + */ + public static func fetchExisting(objects: [T]) -> [T] { + + return self.defaultStack.fetchExisting(objects) + } + + /** + Using the `defaultStack`, fetches the `NSManagedObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`. + + - parameter objectIDs: the `NSManagedObjectID` array for the objects + - returns: the `NSManagedObject` array for objects that exists in the `DataStack` + */ + public static func fetchExisting(objectIDs: [NSManagedObjectID]) -> [T] { + + return self.defaultStack.fetchExisting(objectIDs) + } + /** Using the `defaultStack`, fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. diff --git a/CoreStore/Fetching and Querying/DataStack+Querying.swift b/CoreStore/Fetching and Querying/DataStack+Querying.swift index cdf57a9..cdec5fd 100644 --- a/CoreStore/Fetching and Querying/DataStack+Querying.swift +++ b/CoreStore/Fetching and Querying/DataStack+Querying.swift @@ -34,6 +34,84 @@ public extension DataStack { // MARK: Public + /** + Fetches the `NSManagedObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context. + + - parameter object: a reference to the object created/fetched outside the `DataStack` + - returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found. + */ + public func fetchExisting(object: T) -> T? { + + do { + + return (try self.mainContext.existingObjectWithID(object.objectID) as! T) + } + catch _ { + + return nil + } + } + + /** + Fetches the `NSManagedObject` instance in the `DataStack`'s context from an `NSManagedObjectID`. + + - parameter objectID: the `NSManagedObjectID` for the object + - returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found. + */ + public func fetchExisting(objectID: NSManagedObjectID) -> T? { + + do { + + return (try self.mainContext.existingObjectWithID(objectID) as! T) + } + catch _ { + + return nil + } + } + + /** + Fetches the `NSManagedObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context. + + - parameter objects: an array of `NSManagedObject`s created/fetched outside the `DataStack` + - returns: the `NSManagedObject` array for objects that exists in the `DataStack` + */ + public func fetchExisting(objects: [T]) -> [T] { + + var existingObjects = [T]() + for object in objects { + + do { + + let existingObject = try self.mainContext.existingObjectWithID(object.objectID) as! T + existingObjects.append(existingObject) + } + catch _ { } + } + return existingObjects + } + + /** + Fetches the `NSManagedObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`. + + - parameter objectIDs: the `NSManagedObjectID` array for the objects + - returns: the `NSManagedObject` array for objects that exists in the `DataStack` + */ + public func fetchExisting(objectIDs: [NSManagedObjectID]) -> [T] { + + var existingObjects = [T]() + for objectID in objectIDs { + + do { + + let existingObject = try self.mainContext.existingObjectWithID(objectID) as! T + existingObjects.append(existingObject) + } + catch _ { } + } + return existingObjects + } + /** Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. From 90bfaeaae8cd832422a02a1efa15a9e4c6c18bcc Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 19 Aug 2015 18:30:39 +0900 Subject: [PATCH 15/21] removed unused key tuple --- .../Importing Data/BaseDataTransaction+Importing.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/CoreStore/Importing Data/BaseDataTransaction+Importing.swift b/CoreStore/Importing Data/BaseDataTransaction+Importing.swift index e5d2b93..4fdfe7b 100644 --- a/CoreStore/Importing Data/BaseDataTransaction+Importing.swift +++ b/CoreStore/Importing Data/BaseDataTransaction+Importing.swift @@ -55,7 +55,7 @@ public protocol ImportableUniqueObject: ImportableObject { var uniqueIDValue: UniqueIDType { get set } - static func uniqueIDFromImportSource(source: ImportSource) throws -> (key: String, value: UniqueIDType!) + static func uniqueIDFromImportSource(source: ImportSource) throws -> UniqueIDType } @@ -149,8 +149,7 @@ public extension BaseDataTransaction { } let uniqueIDKeyPath = T.uniqueIDKeyPath - let uniqueIDFromImportSource = try T.uniqueIDFromImportSource(source) - let uniqueIDValue = uniqueIDFromImportSource.value + let uniqueIDValue = try T.uniqueIDFromImportSource(source) if let object = self.fetchOne(From(T), Where(uniqueIDKeyPath, isEqualTo: uniqueIDValue)) { @@ -189,7 +188,7 @@ public extension BaseDataTransaction { return } - let uniqueIDValue = try T.uniqueIDFromImportSource(source).value + let uniqueIDValue = try T.uniqueIDFromImportSource(source) mapping[uniqueIDValue] = source } } @@ -247,7 +246,7 @@ public extension BaseDataTransaction { return } - let uniqueIDValue = try T.uniqueIDFromImportSource(source).value + let uniqueIDValue = try T.uniqueIDFromImportSource(source) mapping[uniqueIDValue] = source sortedIDs.append(uniqueIDValue) } From 3bf34f33dcaabc241108a251d98aae0c97906730 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 19 Aug 2015 20:56:50 +0900 Subject: [PATCH 16/21] added utilities for ListMonitor to optionally extract objects with potentially invalid indexes/indexPaths --- CoreStore/Observing/ListMonitor.swift | 50 ++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/CoreStore/Observing/ListMonitor.swift b/CoreStore/Observing/ListMonitor.swift index f4aa4ad..0528fb1 100644 --- a/CoreStore/Observing/ListMonitor.swift +++ b/CoreStore/Observing/ListMonitor.swift @@ -72,36 +72,84 @@ public final class ListMonitor { // MARK: Public /** - Accesses the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `addObserver(_:)`. + Accesses the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `monitorList(_:)`. - parameter index: the index of the object. Using an index above the valid range will throw an exception. + - returns: the `NSManagedObject` at the specified index */ public subscript(index: Int) -> T { return self[0, index] } + /** + Returns the object at the given index, or `nil` if out of bounds. This subscript indexer is typically used for `ListMonitor`s created with `monitorList(_:)`. + + - parameter index: the index for the object. Using an index above the valid range will return `nil`. + - returns: the `NSManagedObject` at the specified index, or `nil` if out of bounds + */ + public subscript(safeIndex index: Int) -> T? { + + return self[safeSectionIndex: 0, safeItemIndex: index] + } + /** Accesses the object at the given `sectionIndex` and `itemIndex`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. - parameter sectionIndex: the section index for the object. Using a `sectionIndex` with an invalid range will throw an exception. - parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will throw an exception. + - returns: the `NSManagedObject` at the specified section and item index */ public subscript(sectionIndex: Int, itemIndex: Int) -> T { return self[NSIndexPath(forItem: itemIndex, inSection: sectionIndex)] } + /** + Returns the object at the given section and item index, or `nil` if out of bounds. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. + + - parameter sectionIndex: the section index for the object. Using a `sectionIndex` with an invalid range will return `nil`. + - parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will return `nil`. + - returns: the `NSManagedObject` at the specified section and item index, or `nil` if out of bounds + */ + public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> T? { + + guard let sections = self.fetchedResultsController.sections + where sectionIndex < sections.count else { + + return nil + } + + let section = sections[sectionIndex] + guard itemIndex < section.numberOfObjects else { + + return nil + } + return sections[sectionIndex].objects?[itemIndex] as? T + } + /** Accesses the object at the given `NSIndexPath`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. - parameter indexPath: the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will throw an exception. + - returns: the `NSManagedObject` at the specified index path */ public subscript(indexPath: NSIndexPath) -> T { return self.fetchedResultsController.objectAtIndexPath(indexPath) as! T } + /** + Returns the object at the given `NSIndexPath`, or `nil` if out of bounds. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. + + - parameter indexPath: the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will return `nil`. + - returns: the `NSManagedObject` at the specified index path, or `nil` if out of bounds + */ + public subscript(safeIndexPath indexPath: NSIndexPath) -> T? { + + return self[safeSectionIndex: indexPath.section, safeItemIndex: indexPath.item] + } + /** Returns the number of sections */ From 9cfad8a17a0c00e690b313b80e21e7f062d277bf Mon Sep 17 00:00:00 2001 From: John Estropia Date: Wed, 19 Aug 2015 21:00:27 +0900 Subject: [PATCH 17/21] oops, GroupBy should not be needed for monitorList() --- CoreStore/Observing/CoreStore+Observing.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CoreStore/Observing/CoreStore+Observing.swift b/CoreStore/Observing/CoreStore+Observing.swift index ec3f9b5..b1d846b 100644 --- a/CoreStore/Observing/CoreStore+Observing.swift +++ b/CoreStore/Observing/CoreStore+Observing.swift @@ -51,7 +51,7 @@ public extension CoreStore { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public static func monitorList(from: From, _ groupBy: GroupBy? = nil, _ queryClauses: FetchClause...) -> ListMonitor { + public static func monitorList(from: From, _ queryClauses: FetchClause...) -> ListMonitor { return self.defaultStack.monitorList(from, queryClauses) } @@ -63,7 +63,7 @@ public extension CoreStore { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public static func monitorList(from: From, _ groupBy: GroupBy? = nil, _ queryClauses: [FetchClause]) -> ListMonitor { + public static func monitorList(from: From, _ queryClauses: [FetchClause]) -> ListMonitor { return self.defaultStack.monitorList(from, queryClauses) } From 2ed61fdb17f3ef3322a60abfdcf1cc457a0909c4 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Thu, 20 Aug 2015 12:15:20 +0900 Subject: [PATCH 18/21] added utilities to ListMonitor to extract all objects in specified sections --- CoreStore/Observing/ListMonitor.swift | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/CoreStore/Observing/ListMonitor.swift b/CoreStore/Observing/ListMonitor.swift index 0528fb1..f60dc20 100644 --- a/CoreStore/Observing/ListMonitor.swift +++ b/CoreStore/Observing/ListMonitor.swift @@ -150,18 +150,63 @@ public final class ListMonitor { return self[safeSectionIndex: indexPath.section, safeItemIndex: indexPath.item] } + /** + Returns all objects in all sections + + - returns: all objects in all sections + */ + public func objectsInAllSections() -> [T] { + + return (self.fetchedResultsController.fetchedObjects as? [T]) ?? [] + } + + /** + Returns all objects in the specified section + + - parameter section: the section index. Using an index outside the valid range will throw an exception. + - returns: all objects in the specified section + */ + public func objectsInSection(section: Int) -> [T] { + + return (self.fetchedResultsController.sections?[section].objects as? [T]) ?? [] + } + + /** + Returns all objects in the specified section + + - parameter section: the section index. Using an index outside the valid range will return `nil`. + - returns: all objects in the specified section + */ + public func objectsInSection(safeSectionIndex section: Int) -> [T]? { + + return (self.fetchedResultsController.sections?[section].objects as? [T]) ?? [] + } + /** Returns the number of sections + + - returns: the number of sections */ public func numberOfSections() -> Int { return self.fetchedResultsController.sections?.count ?? 0 } + /** + Returns the number of objects in all sections + + - returns: the number of objects in all sections + */ + public func numberOfObjects() -> Int { + + return self.fetchedResultsController.fetchedObjects?.count ?? 0 + } + /** Returns the number of objects in the specified section - parameter section: the section index + - returns: the number of objects in the specified section */ public func numberOfObjectsInSection(section: Int) -> Int { @@ -172,6 +217,7 @@ public final class ListMonitor { Returns the `NSFetchedResultsSectionInfo` for the specified section - parameter section: the section index + - returns: the `NSFetchedResultsSectionInfo` for the specified section */ public func sectionInfoAtIndex(section: Int) -> NSFetchedResultsSectionInfo { From 3348aa0bef2620a97a2f067330de8ba8930e356e Mon Sep 17 00:00:00 2001 From: John Estropia Date: Thu, 20 Aug 2015 13:51:14 +0900 Subject: [PATCH 19/21] added utility to check if objects exist in a ListMonitor --- CoreStore/Observing/ListMonitor.swift | 63 ++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/CoreStore/Observing/ListMonitor.swift b/CoreStore/Observing/ListMonitor.swift index f60dc20..cc2a38f 100644 --- a/CoreStore/Observing/ListMonitor.swift +++ b/CoreStore/Observing/ListMonitor.swift @@ -72,7 +72,7 @@ public final class ListMonitor { // MARK: Public /** - Accesses the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `monitorList(_:)`. + Returns the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `monitorList(_:)`. - parameter index: the index of the object. Using an index above the valid range will throw an exception. - returns: the `NSManagedObject` at the specified index @@ -94,7 +94,7 @@ public final class ListMonitor { } /** - Accesses the object at the given `sectionIndex` and `itemIndex`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. + Returns the object at the given `sectionIndex` and `itemIndex`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. - parameter sectionIndex: the section index for the object. Using a `sectionIndex` with an invalid range will throw an exception. - parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will throw an exception. @@ -129,7 +129,7 @@ public final class ListMonitor { } /** - Accesses the object at the given `NSIndexPath`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. + Returns the object at the given `NSIndexPath`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. - parameter indexPath: the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will throw an exception. - returns: the `NSManagedObject` at the specified index path @@ -150,6 +150,27 @@ public final class ListMonitor { return self[safeSectionIndex: indexPath.section, safeItemIndex: indexPath.item] } + /** + Checks if the `ListMonitor` has at least one object in any section. + + - returns: `true` if at least one object in any section exists, `false` otherwise + */ + public func hasObjects() -> Bool { + + return self.numberOfObjects() > 0 + } + + /** + Checks if the `ListMonitor` has at least one object the specified section. + + - parameter section: the section index. Using an index outside the valid range will return `false`. + - returns: `true` if at least one object in the specified section exists, `false` otherwise + */ + public func hasObjectsInSection(section: Int) -> Bool { + + return self.numberOfObjectsInSection(safeSectionIndex: section) > 0 + } + /** Returns all objects in all sections @@ -172,7 +193,7 @@ public final class ListMonitor { } /** - Returns all objects in the specified section + Returns all objects in the specified section, or `nil` if out of bounds. - parameter section: the section index. Using an index outside the valid range will return `nil`. - returns: all objects in the specified section @@ -205,18 +226,29 @@ public final class ListMonitor { /** Returns the number of objects in the specified section - - parameter section: the section index + - parameter section: the section index. Using an index outside the valid range will throw an exception. - returns: the number of objects in the specified section */ public func numberOfObjectsInSection(section: Int) -> Int { - return self.fetchedResultsController.sections?[section].numberOfObjects ?? 0 + return self.sectionInfoAtIndex(section).numberOfObjects + } + + /** + Returns the number of objects in the specified section, or `nil` if out of bounds. + + - parameter section: the section index. Using an index outside the valid range will return `nil`. + - returns: the number of objects in the specified section + */ + public func numberOfObjectsInSection(safeSectionIndex section: Int) -> Int? { + + return self.sectionInfoAtIndex(safeSectionIndex: section)?.numberOfObjects } /** Returns the `NSFetchedResultsSectionInfo` for the specified section - - parameter section: the section index + - parameter section: the section index. Using an index outside the valid range will throw an exception. - returns: the `NSFetchedResultsSectionInfo` for the specified section */ public func sectionInfoAtIndex(section: Int) -> NSFetchedResultsSectionInfo { @@ -224,6 +256,23 @@ public final class ListMonitor { return self.fetchedResultsController.sections![section] } + /** + Returns the `NSFetchedResultsSectionInfo` for the specified section, or `nil` if out of bounds. + + - parameter section: the section index. Using an index outside the valid range will return `nil`. + - returns: the `NSFetchedResultsSectionInfo` for the specified section, or `nil` if the section index is out of bounds. + */ + public func sectionInfoAtIndex(safeSectionIndex section: Int) -> NSFetchedResultsSectionInfo? { + + guard let sections = self.fetchedResultsController.sections + where section < sections.count else { + + return nil + } + + return sections[section] + } + /** Registers a `ListObserver` to be notified when changes to the receiver's list occur. From 71477c0839aec40834bcd4a3ed35e620f7a9511b Mon Sep 17 00:00:00 2001 From: John Estropia Date: Thu, 20 Aug 2015 14:25:14 +0900 Subject: [PATCH 20/21] allow equality comparison on ListMonitor and ObjectMonitor to help distinguish senders when observing multiple monitors --- CoreStore/Observing/ListMonitor.swift | 10 ++++++++++ CoreStore/Observing/ObjectMonitor.swift | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/CoreStore/Observing/ListMonitor.swift b/CoreStore/Observing/ListMonitor.swift index cc2a38f..3d3fef3 100644 --- a/CoreStore/Observing/ListMonitor.swift +++ b/CoreStore/Observing/ListMonitor.swift @@ -729,6 +729,16 @@ public final class ListMonitor { } +// MARK: - ListMonitor: Equatable + +public func ==(lhs: ListMonitor, rhs: ListMonitor) -> Bool { + + return lhs === rhs +} + +extension ListMonitor: Equatable { } + + // MARK: - ListMonitor: FetchedResultsControllerHandler extension ListMonitor: FetchedResultsControllerHandler { diff --git a/CoreStore/Observing/ObjectMonitor.swift b/CoreStore/Observing/ObjectMonitor.swift index fb1d729..f06d257 100644 --- a/CoreStore/Observing/ObjectMonitor.swift +++ b/CoreStore/Observing/ObjectMonitor.swift @@ -261,6 +261,16 @@ public final class ObjectMonitor { } +// MARK: - ObjectMonitor: Equatable + +public func ==(lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { + + return lhs === rhs +} + +extension ObjectMonitor: Equatable { } + + // MARK: - ObjectMonitor: FetchedResultsControllerHandler extension ObjectMonitor: FetchedResultsControllerHandler { From 2bcf8008c578c91b61e59bd0e30c3b6f8eeebd35 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Thu, 20 Aug 2015 17:20:38 +0900 Subject: [PATCH 21/21] let transaction fetch existing objects from external contexts --- .../BaseDataTransaction+Querying.swift | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/CoreStore/Fetching and Querying/BaseDataTransaction+Querying.swift b/CoreStore/Fetching and Querying/BaseDataTransaction+Querying.swift index 861b805..4f2cd25 100644 --- a/CoreStore/Fetching and Querying/BaseDataTransaction+Querying.swift +++ b/CoreStore/Fetching and Querying/BaseDataTransaction+Querying.swift @@ -33,6 +33,84 @@ public extension BaseDataTransaction { // MARK: Public + /** + Fetches the `NSManagedObject` instance in the transaction's context from a reference created from a transaction or from a different managed object context. + + - parameter object: a reference to the object created/fetched outside the transaction + - returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found. + */ + public func fetchExisting(object: T) -> T? { + + do { + + return (try self.context.existingObjectWithID(object.objectID) as! T) + } + catch _ { + + return nil + } + } + + /** + Fetches the `NSManagedObject` instance in the transaction's context from an `NSManagedObjectID`. + + - parameter objectID: the `NSManagedObjectID` for the object + - returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found. + */ + public func fetchExisting(objectID: NSManagedObjectID) -> T? { + + do { + + return (try self.context.existingObjectWithID(objectID) as! T) + } + catch _ { + + return nil + } + } + + /** + Fetches the `NSManagedObject` instances in the transaction's context from references created from a transaction or from a different managed object context. + + - parameter objects: an array of `NSManagedObject`s created/fetched outside the transaction + - returns: the `NSManagedObject` array for objects that exists in the transaction + */ + public func fetchExisting(objects: [T]) -> [T] { + + var existingObjects = [T]() + for object in objects { + + do { + + let existingObject = try self.context.existingObjectWithID(object.objectID) as! T + existingObjects.append(existingObject) + } + catch _ { } + } + return existingObjects + } + + /** + Fetches the `NSManagedObject` instances in the transaction's context from a list of `NSManagedObjectID`. + + - parameter objectIDs: the `NSManagedObjectID` array for the objects + - returns: the `NSManagedObject` array for objects that exists in the transaction + */ + public func fetchExisting(objectIDs: [NSManagedObjectID]) -> [T] { + + var existingObjects = [T]() + for objectID in objectIDs { + + do { + + let existingObject = try self.context.existingObjectWithID(objectID) as! T + existingObjects.append(existingObject) + } + catch _ { } + } + return existingObjects + } + /** Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.