diff --git a/CoreStore/Observing/ListMonitor.swift b/CoreStore/Observing/ListMonitor.swift index b291126..3bfdaff 100644 --- a/CoreStore/Observing/ListMonitor.swift +++ b/CoreStore/Observing/ListMonitor.swift @@ -354,6 +354,32 @@ public final class ListMonitor { observer.listMonitorDidChange(monitor) } ) + self.registerChangeNotification( + &self.willRefetchListKey, + name: ListMonitorWillRefetchListNotification, + toObserver: observer, + callback: { [weak observer] (monitor) -> Void in + + guard let observer = observer else { + + return + } + observer.listMonitorWillRefetch(monitor) + } + ) + self.registerChangeNotification( + &self.didRefetchListKey, + name: ListMonitorDidRefetchListNotification, + toObserver: observer, + callback: { [weak observer] (monitor) -> Void in + + guard let observer = observer else { + + return + } + observer.listMonitorDidRefetch(monitor) + } + ) } /** @@ -402,6 +428,32 @@ public final class ListMonitor { observer.listMonitorDidChange(monitor) } ) + self.registerChangeNotification( + &self.willRefetchListKey, + name: ListMonitorWillRefetchListNotification, + toObserver: observer, + callback: { [weak observer] (monitor) -> Void in + + guard let observer = observer else { + + return + } + observer.listMonitorWillRefetch(monitor) + } + ) + self.registerChangeNotification( + &self.didRefetchListKey, + name: ListMonitorDidRefetchListNotification, + toObserver: observer, + callback: { [weak observer] (monitor) -> Void in + + guard let observer = observer else { + + return + } + observer.listMonitorDidRefetch(monitor) + } + ) self.registerObjectNotification( &self.didInsertObjectKey, @@ -520,6 +572,32 @@ public final class ListMonitor { observer.listMonitorDidChange(monitor) } ) + self.registerChangeNotification( + &self.willRefetchListKey, + name: ListMonitorWillRefetchListNotification, + toObserver: observer, + callback: { [weak observer] (monitor) -> Void in + + guard let observer = observer else { + + return + } + observer.listMonitorWillRefetch(monitor) + } + ) + self.registerChangeNotification( + &self.didRefetchListKey, + name: ListMonitorDidRefetchListNotification, + toObserver: observer, + callback: { [weak observer] (monitor) -> Void in + + guard let observer = observer else { + + return + } + observer.listMonitorDidRefetch(monitor) + } + ) self.registerObjectNotification( &self.didInsertObjectKey, @@ -644,6 +722,8 @@ public final class ListMonitor { let nilValue: AnyObject? = nil setAssociatedRetainedObject(nilValue, forKey: &self.willChangeListKey, inObject: observer) setAssociatedRetainedObject(nilValue, forKey: &self.didChangeListKey, inObject: observer) + setAssociatedRetainedObject(nilValue, forKey: &self.willRefetchListKey, inObject: observer) + setAssociatedRetainedObject(nilValue, forKey: &self.didRefetchListKey, inObject: observer) setAssociatedRetainedObject(nilValue, forKey: &self.didInsertObjectKey, inObject: observer) setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteObjectKey, inObject: observer) @@ -654,6 +734,54 @@ public final class ListMonitor { setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteSectionKey, inObject: observer) } + /** + Asks the `ListMonitor` to refetch its objects using the specified series of `FetchClause`s. Note that this method does not execute the fetch immediately; the actual fetching will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes. + + `refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes. + + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Note that only specified clauses will be changed; unspecified clauses will use previous values. + */ + public func refetch(fetchClauses: FetchClause...) { + + self.refetch(fetchClauses) + } + + /** + Asks the `ListMonitor` to refetch its objects using the specified series of `FetchClause`s. Note that this method does not execute the fetch immediately; the actual fetching will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes. + + `refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes. + + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Note that only specified clauses will be changed; unspecified clauses will use previous values. + */ + public func refetch(fetchClauses: [FetchClause]) { + + NSNotificationCenter.defaultCenter().postNotificationName( + ListMonitorWillRefetchListNotification, + object: self + ) + + self.taskGroup.notify(.Main) { [weak self] () -> Void in + + guard let strongSelf = self else { + + return + } + + let fetchRequest = strongSelf.fetchedResultsController.fetchRequest + for clause in fetchClauses { + + clause.applyToFetchRequest(fetchRequest) + } + + try! strongSelf.fetchedResultsController.performFetch() + + NSNotificationCenter.defaultCenter().postNotificationName( + ListMonitorDidRefetchListNotification, + object: strongSelf + ) + } + } + // MARK: Internal @@ -705,10 +833,13 @@ public final class ListMonitor { private let fetchedResultsController: NSFetchedResultsController private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate private let sectionIndexTransformer: (sectionName: KeyPath?) -> String? + private let taskGroup = GCDGroup() private weak var parentStack: DataStack? private var willChangeListKey: Void? private var didChangeListKey: Void? + private var willRefetchListKey: Void? + private var didRefetchListKey: Void? private var didInsertObjectKey: Void? private var didDeleteObjectKey: Void? @@ -888,6 +1019,7 @@ extension ListMonitor: FetchedResultsControllerHandler { private func controllerWillChangeContent(controller: NSFetchedResultsController) { + self.taskGroup.enter() NSNotificationCenter.defaultCenter().postNotificationName( ListMonitorWillChangeListNotification, object: self @@ -900,6 +1032,7 @@ extension ListMonitor: FetchedResultsControllerHandler { ListMonitorDidChangeListNotification, object: self ) + self.taskGroup.leave() } private func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? { @@ -978,6 +1111,8 @@ private final class FetchedResultsControllerDelegate: NSObject, NSFetchedResults private let ListMonitorWillChangeListNotification = "ListMonitorWillChangeListNotification" private let ListMonitorDidChangeListNotification = "ListMonitorDidChangeListNotification" +private let ListMonitorWillRefetchListNotification = "ListMonitorWillRefetchListNotification" +private let ListMonitorDidRefetchListNotification = "ListMonitorDidRefetchListNotification" private let ListMonitorDidInsertObjectNotification = "ListMonitorDidInsertObjectNotification" private let ListMonitorDidDeleteObjectNotification = "ListMonitorDidDeleteObjectNotification" diff --git a/CoreStore/Observing/ListObserver.swift b/CoreStore/Observing/ListObserver.swift index dc97556..6ce085c 100644 --- a/CoreStore/Observing/ListObserver.swift +++ b/CoreStore/Observing/ListObserver.swift @@ -58,6 +58,20 @@ public protocol ListObserver: class { - parameter monitor: the `ListMonitor` monitoring the object being observed */ func listMonitorDidChange(monitor: ListMonitor) + + /** + This method is broadcast from within the `ListMonitor`'s `refetch(...)` method to let observers prepare for the internal `NSFetchedResultsController`'s pending change to its predicate, sort descriptors, etc. Note that the actual refetch will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes. + + - parameter monitor: the `ListMonitor` monitoring the object being observed + */ + func listMonitorWillRefetch(monitor: ListMonitor) + + /** + After the `ListMonitor`'s `refetch(...)` method is called, this method is broadcast after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes. + + - parameter monitor: the `ListMonitor` monitoring the object being observed + */ + func listMonitorDidRefetch(monitor: ListMonitor) } public extension ListObserver { @@ -71,6 +85,16 @@ public extension ListObserver { The default implementation does nothing. */ func listMonitorDidChange(monitor: ListMonitor) { } + + /** + The default implementation does nothing. + */ + func listMonitorWillRefetch(monitor: ListMonitor) { } + + /** + The default implementation does nothing. + */ + func listMonitorDidRefetch(monitor: ListMonitor) { } } diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift index 58c754d..49d2473 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift @@ -12,6 +12,41 @@ import CoreStore private struct Static { + enum Filter: String { + + case All = "All Colors" + case Light = "Light Colors" + case Dark = "Dark Colors" + + func next() -> Filter { + + switch self { + + case All: return .Light + case Light: return .Dark + case Dark: return .All + } + } + + func whereClause() -> Where { + + switch self { + + case .All: return Where(true) + case .Light: return Where("brightness >= 0.9") + case .Dark: return Where("brightness <= 0.4") + } + } + } + + static var filter = Filter.All { + + didSet { + + self.palettes.refetch(self.filter.whereClause()) + } + } + static let palettes: ListMonitor = { try! CoreStore.addSQLiteStoreAndWait( @@ -47,7 +82,8 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver super.viewDidLoad() - self.navigationItem.leftBarButtonItems = [ + let navigationItem = self.navigationItem + navigationItem.leftBarButtonItems = [ self.editButtonItem(), UIBarButtonItem( barButtonSystemItem: .Trash, @@ -55,11 +91,22 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver action: "resetBarButtonItemTouched:" ) ] - self.navigationItem.rightBarButtonItem = UIBarButtonItem( - barButtonSystemItem: .Add, + + let filterBarButton = UIBarButtonItem( + title: Static.filter.rawValue, + style: .Plain, target: self, - action: "addBarButtonItemTouched:" + action: "filterBarButtonItemTouched:" ) + navigationItem.rightBarButtonItems = [ + UIBarButtonItem( + barButtonSystemItem: .Add, + target: self, + action: "addBarButtonItemTouched:" + ), + filterBarButton + ] + self.filterBarButton = filterBarButton Static.palettes.addObserver(self) } @@ -150,6 +197,18 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver self.tableView.endUpdates() } + func listMonitorWillRefetch(monitor: ListMonitor) { + + self.setTableEnabled(false) + } + + func listMonitorDidRefetch(monitor: ListMonitor) { + + self.filterBarButton?.title = Static.filter.rawValue + self.tableView.reloadData() + self.setTableEnabled(true) + } + // MARK: ListObjectObserver @@ -195,6 +254,8 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver // MARK: Private + private var filterBarButton: UIBarButtonItem? + @IBAction private dynamic func resetBarButtonItemTouched(sender: AnyObject?) { CoreStore.beginAsynchronous { (transaction) -> Void in @@ -204,6 +265,11 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver } } + @IBAction private dynamic func filterBarButtonItemTouched(sender: AnyObject?) { + + Static.filter = Static.filter.next() + } + @IBAction private dynamic func addBarButtonItemTouched(sender: AnyObject?) { CoreStore.beginAsynchronous { (transaction) -> Void in @@ -214,5 +280,23 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver transaction.commit() } } + + private func setTableEnabled(enabled: Bool) { + + UIView.animateWithDuration( + 0.2, + delay: 0, + options: .BeginFromCurrentState, + animations: { () -> Void in + + if let tableView = self.tableView { + + tableView.alpha = enabled ? 1.0 : 0.5 + tableView.userInteractionEnabled = enabled + } + }, + completion: nil + ) + } }