From 718d2c9b7d464afe7af79b54bb114da6a8dc5fae Mon Sep 17 00:00:00 2001 From: John Estropia Date: Tue, 24 Nov 2015 14:49:43 +0900 Subject: [PATCH] Added ability to initialize ListMonitors asynchronously. This is a deadlock-preventive measure for apps that heavily recreates ListMonitors while updates and saves are running in the background (because NSFetchedResultController's performFetch() locks the whole NSManagedObjectContext chain up until the NSPersistentStoreCoordinator) --- CoreStore.podspec | 2 +- CoreStore/Info.plist | 2 +- CoreStore/Observing/CoreStore+Observing.swift | 50 ++++++++++++ CoreStore/Observing/DataStack+Observing.swift | 80 +++++++++++++++++++ CoreStore/Observing/ListMonitor.swift | 37 ++++++++- README.md | 2 +- 6 files changed, 168 insertions(+), 5 deletions(-) diff --git a/CoreStore.podspec b/CoreStore.podspec index fba19e6..4378141 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" diff --git a/CoreStore/Info.plist b/CoreStore/Info.plist index 009ff7e..2e9a7c2 100644 --- a/CoreStore/Info.plist +++ b/CoreStore/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.3.3 + 1.3.4 CFBundleSignature ???? CFBundleVersion 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 662c857..73006bd 100644 --- a/CoreStore/Observing/DataStack+Observing.swift +++ b/CoreStore/Observing/DataStack+Observing.swift @@ -97,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. @@ -138,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 4158479..98a41a0 100644 --- a/CoreStore/Observing/ListMonitor.swift +++ b/CoreStore/Observing/ListMonitor.swift @@ -885,7 +885,39 @@ 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) { + + 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 context = dataStack.mainContext @@ -927,7 +959,8 @@ public final class ListMonitor { fetchedResultsControllerDelegate.handler = self fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController - try! fetchedResultsController.performFetch() + + prepareFetch(self, { try! fetchedResultsController.performFetch() }) } deinit { diff --git a/README.md b/README.md index 263fa4c..50cdc96 100644 --- a/README.md +++ b/README.md @@ -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(