diff --git a/CoreStore.podspec b/CoreStore.podspec index e2d0391..5174ecf 100644 --- a/CoreStore.podspec +++ b/CoreStore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CoreStore" - s.version = "1.3.3" + s.version = "1.3.4" s.license = "MIT" s.summary = "Unleashing the real power of Core Data with the elegance and safety of Swift" s.homepage = "https://github.com/JohnEstropia/CoreStore" @@ -12,8 +12,10 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.0" s.source_files = "CoreStore", "CoreStore/**/*.{swift}" - s.osx.exclude_files = "CoreStore/Observing/*.{swift}", "CoreStore/Internal/FetchedResultsControllerDelegate.swift" + s.osx.exclude_files = "CoreStore/Observing/*.{swift}", "CoreStore/Internal/FetchedResultsControllerDelegate.swift", "CoreStore/Internal/NSFetchedResultsController+Convenience.swift" s.frameworks = "Foundation", "CoreData" s.requires_arc = true - s.dependency "GCDKit", "1.1.3" + s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS' => '-D USE_FRAMEWORKS' } + + s.dependency "GCDKit", "1.1.4" end \ No newline at end of file diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 7b5aff8..20d777b 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; }; B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */; }; B51BE06A1B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */; }; + B5202CFA1C04688100DED140 /* NSFetchedResultsController+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */; }; + B5202CFD1C046E8400DED140 /* NSFetchedResultsController+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */; }; B52DD17E1BE1F8CD00949AFE /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B52DD1741BE1F8CC00949AFE /* CoreStore.framework */; }; B52DD1901BE1F8E600949AFE /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D808021A34715700A44484 /* GCDKit.framework */; }; B52DD1911BE1F8EB00949AFE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5548CD51BD65AE00077652A /* Foundation.framework */; }; @@ -274,6 +276,7 @@ 2F291E2619C6D3CF007AF63F /* CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CoreStore.swift; sourceTree = ""; }; B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = ""; }; B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectModel+Setup.swift"; sourceTree = ""; }; + B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSFetchedResultsController+Convenience.swift"; sourceTree = ""; }; B52DD1741BE1F8CC00949AFE /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B52DD17D1BE1F8CC00949AFE /* CoreStoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreStoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B54A6A541BA15F2A007870FD /* FetchedResultsControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchedResultsControllerDelegate.swift; sourceTree = ""; }; @@ -295,7 +298,7 @@ B5D39A0119FD00C9000E91BB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 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; }; + B5D9C8F61B160ED200E64F0E /* CoreStore.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; 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; }; @@ -617,6 +620,7 @@ children = ( B5E84F271AFF84920064E85B /* NSManagedObject+Convenience.swift */, B5FAD6A81B50A4B300714891 /* NSProgress+Convenience.swift */, + B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */, ); path = "Convenience Helpers"; sourceTree = ""; @@ -918,6 +922,7 @@ B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */, B56007111B3F6BD500A9A8F9 /* Into.swift in Sources */, B5E84F111AFF847B0064E85B /* Select.swift in Sources */, + B5202CFA1C04688100DED140 /* NSFetchedResultsController+Convenience.swift in Sources */, B5E84EE11AFF84500064E85B /* PersistentStoreResult.swift in Sources */, B5E84F251AFF84860064E85B /* ObjectObserver.swift in Sources */, B5E84F2F1AFF849C0064E85B /* NotificationObserver.swift in Sources */, @@ -1056,6 +1061,7 @@ B56321811BD65216006C9394 /* DataStack.swift in Sources */, B56321A81BD65219006C9394 /* NSManagedObject+Convenience.swift in Sources */, B56321981BD65216006C9394 /* Where.swift in Sources */, + B5202CFD1C046E8400DED140 /* NSFetchedResultsController+Convenience.swift in Sources */, B56321AF1BD6521C006C9394 /* NSFileManager+Setup.swift in Sources */, B56321971BD65216006C9394 /* Select.swift in Sources */, B56321AB1BD6521C006C9394 /* FetchedResultsControllerDelegate.swift in Sources */, @@ -1169,6 +1175,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "-D USE_FRAMEWORKS -D DEBUG"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1208,6 +1215,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.1; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_SWIFT_FLAGS = "-D USE_FRAMEWORKS"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1230,7 +1238,6 @@ 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_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = CoreStore; SKIP_INSTALL = YES; diff --git a/CoreStore/Convenience Helpers/NSFetchedResultsController+Convenience.swift b/CoreStore/Convenience Helpers/NSFetchedResultsController+Convenience.swift new file mode 100644 index 0000000..0b468b2 --- /dev/null +++ b/CoreStore/Convenience Helpers/NSFetchedResultsController+Convenience.swift @@ -0,0 +1,50 @@ +// +// NSManagedObject+Convenience.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 + + +// MARK: - NSFetchedResultsController + +public extension NSFetchedResultsController { + + public convenience init(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) { + + let context = dataStack.mainContext + from?.applyToFetchRequest(fetchRequest, context: context) + for clause in fetchClauses { + + clause.applyToFetchRequest(fetchRequest) + } + + self.init( + fetchRequest: fetchRequest, + managedObjectContext: context, + sectionNameKeyPath: sectionBy?.sectionKeyPath, + cacheName: nil + ) + } +} diff --git a/CoreStore/Convenience Helpers/NSProgress+Convenience.swift b/CoreStore/Convenience Helpers/NSProgress+Convenience.swift index 0d811b3..b2fd744 100644 --- a/CoreStore/Convenience Helpers/NSProgress+Convenience.swift +++ b/CoreStore/Convenience Helpers/NSProgress+Convenience.swift @@ -24,7 +24,9 @@ // import Foundation -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - NSProgress diff --git a/CoreStore/CoreStore.swift b/CoreStore/CoreStore.swift index 532c54e..37e0e58 100644 --- a/CoreStore/CoreStore.swift +++ b/CoreStore/CoreStore.swift @@ -24,7 +24,9 @@ // import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - CoreStore diff --git a/CoreStore/Fetching and Querying/DataStack+Querying.swift b/CoreStore/Fetching and Querying/DataStack+Querying.swift index 5408b98..9161a25 100644 --- a/CoreStore/Fetching and Querying/DataStack+Querying.swift +++ b/CoreStore/Fetching and Querying/DataStack+Querying.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - DataStack diff --git a/CoreStore/Internal/NSManagedObjectContext+CoreStore.swift b/CoreStore/Internal/NSManagedObjectContext+CoreStore.swift index 30ee6a6..f9b1f17 100644 --- a/CoreStore/Internal/NSManagedObjectContext+CoreStore.swift +++ b/CoreStore/Internal/NSManagedObjectContext+CoreStore.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - NSManagedObjectContext diff --git a/CoreStore/Internal/NSManagedObjectContext+Transaction.swift b/CoreStore/Internal/NSManagedObjectContext+Transaction.swift index 17b5676..cd0f03a 100644 --- a/CoreStore/Internal/NSManagedObjectContext+Transaction.swift +++ b/CoreStore/Internal/NSManagedObjectContext+Transaction.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - NSManagedObjectContext diff --git a/CoreStore/Internal/NSManagedObjectModel+Setup.swift b/CoreStore/Internal/NSManagedObjectModel+Setup.swift index ace2548..b975888 100644 --- a/CoreStore/Internal/NSManagedObjectModel+Setup.swift +++ b/CoreStore/Internal/NSManagedObjectModel+Setup.swift @@ -65,10 +65,13 @@ internal extension NSManagedObjectModel { } else if let resolvedVersion = modelVersions.first ?? modelVersionHints.first { - CoreStore.log( - .Warning, - message: "The MigrationChain leaf versions do not include any of the model file's embedded versions. Resolving to version \"\(resolvedVersion)\"." - ) + if !modelVersionHints.isEmpty { + + CoreStore.log( + .Warning, + message: "The MigrationChain leaf versions do not include any of the model file's embedded versions. Resolving to version \"\(resolvedVersion)\"." + ) + } currentModelVersion = resolvedVersion } else { diff --git a/CoreStore/Migrating/CoreStore+Migration.swift b/CoreStore/Migrating/CoreStore+Migration.swift index bd909b2..6f4d0dc 100644 --- a/CoreStore/Migrating/CoreStore+Migration.swift +++ b/CoreStore/Migrating/CoreStore+Migration.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - CoreStore diff --git a/CoreStore/Migrating/DataStack+Migration.swift b/CoreStore/Migrating/DataStack+Migration.swift index 6e6ff37..8c82604 100644 --- a/CoreStore/Migrating/DataStack+Migration.swift +++ b/CoreStore/Migrating/DataStack+Migration.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - DataStack diff --git a/CoreStore/Observing/CoreStore+Observing.swift b/CoreStore/Observing/CoreStore+Observing.swift index df5b735..90303bf 100644 --- a/CoreStore/Observing/CoreStore+Observing.swift +++ b/CoreStore/Observing/CoreStore+Observing.swift @@ -72,6 +72,30 @@ public extension CoreStore { return self.defaultStack.monitorList(from, queryClauses) } + /** + Using the `defaultStack`, asynchronously creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed. + + - parameter createAsynchronously: the closure that receives the created `ListMonitor` instance + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + */ + public static func monitorList(createAsynchronously createAsynchronously: (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { + + self.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses) + } + + /** + Using the `defaultStack`, asynchronously creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed. + + - parameter createAsynchronously: the closure that receives the created `ListMonitor` instance + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + */ + public static func monitorList(createAsynchronously createAsynchronously: (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { + + self.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses) + } + /** Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. @@ -99,4 +123,30 @@ public extension CoreStore { return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses) } + + /** + Using the `defaultStack`, asynchronously creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed. + + - parameter createAsynchronously: the closure that receives the created `ListMonitor` instance + - parameter from: a `From` clause indicating the entity type + - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + */ + public static func monitorSectionedList(createAsynchronously createAsynchronously: (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { + + self.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses) + } + + /** + Using the `defaultStack`, asynchronously creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed. + + - parameter createAsynchronously: the closure that receives the created `ListMonitor` instance + - parameter from: a `From` clause indicating the entity type + - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + */ + public static func monitorSectionedList(createAsynchronously createAsynchronously: (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { + + self.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses) + } } diff --git a/CoreStore/Observing/DataStack+Observing.swift b/CoreStore/Observing/DataStack+Observing.swift index 4b7b039..73006bd 100644 --- a/CoreStore/Observing/DataStack+Observing.swift +++ b/CoreStore/Observing/DataStack+Observing.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - DataStack @@ -95,6 +97,45 @@ public extension DataStack { ) } + /** + Asynchronously creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed. + + - parameter createAsynchronously: the closure that receives the created `ListMonitor` instance + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + */ + public func monitorList(createAsynchronously createAsynchronously: (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { + + self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses) + } + + /** + Asynchronously creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed. + + - parameter createAsynchronously: the closure that receives the created `ListMonitor` instance + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + */ + public func monitorList(createAsynchronously createAsynchronously: (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { + + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to observe objects from \(typeName(self)) outside the main thread." + ) + CoreStore.assert( + fetchClauses.filter { $0 is OrderBy }.count > 0, + "A ListMonitor requires an OrderBy clause." + ) + + _ = ListMonitor( + dataStack: self, + from: from, + sectionBy: nil, + fetchClauses: fetchClauses, + createAsynchronously: createAsynchronously + ) + } + /** Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. @@ -136,4 +177,45 @@ public extension DataStack { fetchClauses: fetchClauses ) } + + /** + Asynchronously creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed. + + - parameter createAsynchronously: the closure that receives the created `ListMonitor` instance + - parameter from: a `From` clause indicating the entity type + - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + */ + public func monitorSectionedList(createAsynchronously createAsynchronously: (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { + + self.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses) + } + + /** + Asynchronously creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed. + + - parameter createAsynchronously: the closure that receives the created `ListMonitor` instance + - parameter from: a `From` clause indicating the entity type + - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + */ + public func monitorSectionedList(createAsynchronously createAsynchronously: (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { + + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to observe objects from \(typeName(self)) outside the main thread." + ) + CoreStore.assert( + fetchClauses.filter { $0 is OrderBy }.count > 0, + "A ListMonitor requires an OrderBy clause." + ) + + _ = ListMonitor( + dataStack: self, + from: from, + sectionBy: sectionBy, + fetchClauses: fetchClauses, + createAsynchronously: createAsynchronously + ) + } } diff --git a/CoreStore/Observing/ListMonitor.swift b/CoreStore/Observing/ListMonitor.swift index 7457388..d2a6179 100644 --- a/CoreStore/Observing/ListMonitor.swift +++ b/CoreStore/Observing/ListMonitor.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - ListMonitor @@ -883,29 +885,53 @@ public final class ListMonitor { // MARK: Internal - internal init(dataStack: DataStack, from: From, sectionBy: SectionBy?, fetchClauses: [FetchClause]) { + internal convenience init(dataStack: DataStack, from: From, sectionBy: SectionBy?, fetchClauses: [FetchClause]) { + + self.init( + dataStack: dataStack, + from: from, + sectionBy: sectionBy, + fetchClauses: fetchClauses, + prepareFetch: { _, performFetch in performFetch() } + ) + } + + internal convenience init(dataStack: DataStack, from: From, sectionBy: SectionBy?, fetchClauses: [FetchClause], createAsynchronously: (ListMonitor) -> Void) { - let context = dataStack.mainContext + self.init( + dataStack: dataStack, + from: from, + sectionBy: sectionBy, + fetchClauses: fetchClauses, + prepareFetch: { listMonitor, performFetch in + + dataStack.childTransactionQueue.async { + + performFetch() + GCDQueue.Main.async { + + createAsynchronously(listMonitor) + } + } + } + ) + } + + private init(dataStack: DataStack, from: From, sectionBy: SectionBy?, fetchClauses: [FetchClause], prepareFetch: (ListMonitor, () -> Void) -> Void) { let fetchRequest = NSFetchRequest() - from.applyToFetchRequest(fetchRequest, context: context) - fetchRequest.fetchLimit = 0 fetchRequest.resultType = .ManagedObjectResultType fetchRequest.fetchBatchSize = 20 fetchRequest.includesPendingChanges = false fetchRequest.shouldRefreshRefetchedObjects = true - for clause in fetchClauses { - - clause.applyToFetchRequest(fetchRequest) - } - let fetchedResultsController = NSFetchedResultsController( + dataStack: dataStack, fetchRequest: fetchRequest, - managedObjectContext: context, - sectionNameKeyPath: sectionBy?.sectionKeyPath, - cacheName: nil + from: from, + sectionBy: sectionBy, + fetchClauses: fetchClauses ) let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate() @@ -925,7 +951,8 @@ public final class ListMonitor { fetchedResultsControllerDelegate.handler = self fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController - try! fetchedResultsController.performFetch() + + prepareFetch(self, { try! fetchedResultsController.performFetch() }) } deinit { diff --git a/CoreStore/Observing/ListObserver.swift b/CoreStore/Observing/ListObserver.swift index 7d33a18..b10b1c1 100644 --- a/CoreStore/Observing/ListObserver.swift +++ b/CoreStore/Observing/ListObserver.swift @@ -205,7 +205,6 @@ public protocol ListSectionObserver: ListObjectObserver { - parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the inserted section - parameter sectionIndex: the new section index for the new section */ - @available(iOS 8.0, *) func listMonitor(monitor: ListMonitor, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) /** @@ -215,7 +214,6 @@ public protocol ListSectionObserver: ListObjectObserver { - parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the deleted section - parameter sectionIndex: the previous section index for the deleted section */ - @available(iOS 8.0, *) func listMonitor(monitor: ListMonitor, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) } diff --git a/CoreStore/Observing/ObjectMonitor.swift b/CoreStore/Observing/ObjectMonitor.swift index ada879a..6458b35 100644 --- a/CoreStore/Observing/ObjectMonitor.swift +++ b/CoreStore/Observing/ObjectMonitor.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - ObjectMonitor @@ -165,25 +167,18 @@ public final class ObjectMonitor { internal init(dataStack: DataStack, object: T) { - let context = dataStack.mainContext - let fetchRequest = NSFetchRequest() fetchRequest.entity = object.entity - fetchRequest.fetchLimit = 0 fetchRequest.resultType = .ManagedObjectResultType fetchRequest.sortDescriptors = [] fetchRequest.includesPendingChanges = false fetchRequest.shouldRefreshRefetchedObjects = true - let originalObjectID = object.objectID - Where("SELF", isEqualTo: originalObjectID).applyToFetchRequest(fetchRequest) - let fetchedResultsController = NSFetchedResultsController( + dataStack: dataStack, fetchRequest: fetchRequest, - managedObjectContext: context, - sectionNameKeyPath: nil, - cacheName: nil + fetchClauses: [Where("SELF", isEqualTo: object.objectID)] ) let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate() diff --git a/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift b/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift index a486b62..945cff4 100644 --- a/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift +++ b/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - AsynchronousDataTransaction diff --git a/CoreStore/Saving and Processing/BaseDataTransaction.swift b/CoreStore/Saving and Processing/BaseDataTransaction.swift index 0aad856..65934dc 100644 --- a/CoreStore/Saving and Processing/BaseDataTransaction.swift +++ b/CoreStore/Saving and Processing/BaseDataTransaction.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - BaseDataTransaction diff --git a/CoreStore/Saving and Processing/DataStack+Transaction.swift b/CoreStore/Saving and Processing/DataStack+Transaction.swift index aa00a64..789b157 100644 --- a/CoreStore/Saving and Processing/DataStack+Transaction.swift +++ b/CoreStore/Saving and Processing/DataStack+Transaction.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - DataStack diff --git a/CoreStore/Saving and Processing/SynchronousDataTransaction.swift b/CoreStore/Saving and Processing/SynchronousDataTransaction.swift index bace899..08bcf57 100644 --- a/CoreStore/Saving and Processing/SynchronousDataTransaction.swift +++ b/CoreStore/Saving and Processing/SynchronousDataTransaction.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - SynchronousDataTransaction diff --git a/CoreStore/Saving and Processing/UnsafeDataTransaction.swift b/CoreStore/Saving and Processing/UnsafeDataTransaction.swift index 4298b01..3ef3703 100644 --- a/CoreStore/Saving and Processing/UnsafeDataTransaction.swift +++ b/CoreStore/Saving and Processing/UnsafeDataTransaction.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif @available(*, deprecated=1.3.1, renamed="UnsafeDataTransaction") diff --git a/CoreStore/Setting Up/CoreStore+Setup.swift b/CoreStore/Setting Up/CoreStore+Setup.swift index c54c387..bd8364c 100644 --- a/CoreStore/Setting Up/CoreStore+Setup.swift +++ b/CoreStore/Setting Up/CoreStore+Setup.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif // MARK: - CoreStore diff --git a/CoreStore/Setting Up/DataStack.swift b/CoreStore/Setting Up/DataStack.swift index d46dc7f..a65c5e2 100644 --- a/CoreStore/Setting Up/DataStack.swift +++ b/CoreStore/Setting Up/DataStack.swift @@ -25,7 +25,9 @@ import Foundation import CoreData -import GCDKit +#if USE_FRAMEWORKS + import GCDKit +#endif internal let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first! diff --git a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj index 8693038..b66c960 100644 --- a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj +++ b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj @@ -48,6 +48,20 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + B5202CF11C044CC800DED140 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B52DD1741BE1F8CC00949AFE; + remoteInfo = "CoreStore OSX"; + }; + B5202CF31C044CC800DED140 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B52DD17D1BE1F8CC00949AFE; + remoteInfo = "CoreStoreTests OSX"; + }; B56321C51BD65965006C9394 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */; @@ -286,8 +300,10 @@ isa = PBXGroup; children = ( B583A91B1AF5F4F4001F76AF /* CoreStore.framework */, - B583A91D1AF5F4F4001F76AF /* CoreStoreTests iOS.xctest */, + B583A91D1AF5F4F4001F76AF /* CoreStoreTests.xctest */, B56321C61BD65965006C9394 /* CoreStore.framework */, + B5202CF21C044CC800DED140 /* CoreStore.framework */, + B5202CF41C044CC800DED140 /* CoreStoreTests.xctest */, ); name = Products; sourceTree = ""; @@ -355,6 +371,20 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ + B5202CF21C044CC800DED140 /* CoreStore.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = CoreStore.framework; + remoteRef = B5202CF11C044CC800DED140 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + B5202CF41C044CC800DED140 /* CoreStoreTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = CoreStoreTests.xctest; + remoteRef = B5202CF31C044CC800DED140 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; B56321C61BD65965006C9394 /* CoreStore.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; @@ -369,10 +399,10 @@ remoteRef = B583A91A1AF5F4F4001F76AF /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - B583A91D1AF5F4F4001F76AF /* CoreStoreTests iOS.xctest */ = { + B583A91D1AF5F4F4001F76AF /* CoreStoreTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; - path = "CoreStoreTests iOS.xctest"; + path = CoreStoreTests.xctest; remoteRef = B583A91C1AF5F4F4001F76AF /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; diff --git a/Libraries/GCDKit b/Libraries/GCDKit index 6b72512..5c5b4c1 160000 --- a/Libraries/GCDKit +++ b/Libraries/GCDKit @@ -1 +1 @@ -Subproject commit 6b72512258092d76e6e515672a308b8ec34257ae +Subproject commit 5c5b4c178e6c47df98cf0444364fa5a48f4c93ca diff --git a/README.md b/README.md index fc1ebf7..50cdc96 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Unleashing the real power of Core Data with the elegance and safety of Swift - Documentation! No magic here; all public classes, functions, properties, etc. have detailed Apple Docs. This README also introduces a lot of concepts and explains a lot of CoreStore's behavior. - **New in 1.3.0:** Efficient importing utilities! -**CoreStore's goal is not to expose shorter, magical syntax, but to provide an API that focuses on readability, consistency, and safety.** +**[Or vote for the next feature!](http://goo.gl/RIiHMP)** @@ -255,7 +255,7 @@ class MyViewController: UIViewController { ## Migrations -So far we have only seen `addSQLiteStoreAndWait(...)` used to initialize our persistent store. As the method name's "AndWait" suffix suggests, this method will block, even if a migration occurs. If migrations are expected, the asynchronous variant `addSQLiteStore(... completion:)` method is recommended: +So far we have only seen `addSQLiteStoreAndWait(...)` used to initialize our persistent store. As the method name's "AndWait" suffix suggests, this method blocks so it should not do long tasks such as store migrations (in fact CoreStore won't even attempt to, and any model mismatch will be reported as an error). If migrations are expected, the asynchronous variant `addSQLiteStore(... completion:)` method should be used instead: ```swift do { let progress: NSProgress = try dataStack.addSQLiteStore(