-
-
Notifications
You must be signed in to change notification settings - Fork 255
Observing changes and notifications
CoreStore provides type-safe wrappers for observing managed objects:
-
ObjectMonitor
: use to monitor changes to a singleNSManagedObject
instance (instead of Key-Value Observing) -
ListMonitor
: use to monitor changes to a list ofNSManagedObject
instances (instead ofNSFetchedResultsController
)
To observe an object, implement the ObjectObserver
protocol and specify the EntityType
:
class MyViewController: UIViewController, ObjectObserver {
func objectMonitor(_ monitor: ObjectMonitor<ObjectEntityType>, willUpdateObject object: ObjectEntityType) {
// ...
}
func objectMonitor(_ monitor: ObjectMonitor<ObjectEntityType>, didUpdateObject object: ObjectEntityType, changedPersistentKeys: Set<KeyPathString>) {
// ...
}
func objectMonitor(_ monitor: ObjectMonitor<ObjectEntityType>, didDeleteObject object: ObjectEntityType) {
// ...
}
}
We then need to keep a ObjectMonitor
instance and register our ObjectObserver
as an observer:
let person: MyPersonEntity = // ...
self.monitor = CoreStore.monitorObject(person)
self.monitor.addObserver(self)
The controller will then notify our observer whenever the object's attributes change. You can add multiple ObjectObserver
s to a single ObjectMonitor
without any problem. This means you can just share around the ObjectMonitor
instance to different screens without problem.
You can get ObjectMonitor
's object through its object
property. If the object is deleted, the object
property will become nil
to prevent further access.
While ObjectMonitor
exposes removeObserver(...)
as well, it only stores weak
references of the observers and will safely unregister deallocated observers.
To observe a list of objects, implement one of the ListObserver
protocols and specify the EntityType
:
class MyViewController: UIViewController, ListObserver {
func listMonitorWillChange(_ monitor: ListMonitor<ListEntityType>) {
// ...
}
func listMonitorDidChange(_ monitor: ListMonitor<ListEntityType>) {
// ...
}
}
Including ListObserver
, there are 3 observer protocols you can implement depending on how detailed you need to handle a change notification:
-
ListObserver
: lets you handle these callback methods:
func listMonitorWillChange(_ monitor: ListMonitor<ListEntityType>)
func listMonitorDidChange(_ monitor: ListMonitor<ListEntityType>)
-
ListObjectObserver
: in addition toListObserver
methods, also lets you handle object inserts, updates, and deletes:
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: NSIndexPath)
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: NSIndexPath)
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: NSIndexPath)
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didMoveObject object: ListEntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
-
ListSectionObserver
: in addition toListObjectObserver
methods, also lets you handle section inserts and deletes:
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
We then need to create a ListMonitor
instance and register our ListObserver
as an observer:
self.monitor = CoreStore.monitorList(
From(MyPersonEntity),
Where("age > 30"),
OrderBy(.Ascending("name")),
Tweak { (fetchRequest) -> Void in
fetchRequest.fetchBatchSize = 20
}
)
self.monitor.addObserver(self)
Similar to ObjectMonitor
, a ListMonitor
can also have multiple ListObserver
s registered to a single ListMonitor
.
If you have noticed, the monitorList(...)
method accepts Where
, OrderBy
, and Tweak
clauses exactly like a fetch. As the list maintained by ListMonitor
needs to have a deterministic order, at least the From
and OrderBy
clauses are required.
A ListMonitor
created from monitorList(...)
will maintain a single-section list. You can therefore access its contents with just an index:
let firstPerson = self.monitor[0]
If the list needs to be grouped into sections, create the ListMonitor
instance with the monitorSectionedList(...)
method and a SectionBy
clause:
self.monitor = CoreStore.monitorSectionedList(
From(MyPersonEntity),
SectionBy("age"),
Where("gender", isEqualTo: "M"),
OrderBy(.Ascending("age"), .Ascending("name")),
Tweak { (fetchRequest) -> Void in
fetchRequest.fetchBatchSize = 20
}
)
A list controller created this way will group the objects by the attribute key indicated by the SectionBy
clause. One more thing to remember is that the OrderBy
clause should sort the list in such a way that the SectionBy
attribute would be sorted together (a requirement shared by NSFetchedResultsController
.)
The SectionBy
clause can also be passed a closure to transform the section name into a displayable string:
self.monitor = CoreStore.monitorSectionedList(
From(MyPersonEntity),
SectionBy("age") { (sectionName) -> String? in
"\(sectionName) years old"
},
OrderBy(.Ascending("age"), .Ascending("name"))
)
This is useful when implementing a UITableViewDelegate
's section header:
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionInfo = self.monitor.sectionInfoAtIndex(section)
// sectionInfo is an NSFetchedResultsSectionInfo instance
return sectionInfo.name
}
To access the objects of a sectioned list, use an NSIndexPath
or a tuple:
let indexPath = NSIndexPath(forRow: 2, inSection: 1)
let person1 = self.monitor[indexPath]
let person2 = self.monitor[1, 2]
// person1 and person2 are the same object