Skip to content

Observing changes and notifications

Mikhail Igonin edited this page Aug 1, 2018 · 5 revisions

CoreStore provides type-safe wrappers for observing managed objects:

  • ObjectMonitor: use to monitor changes to a single NSManagedObject instance (instead of Key-Value Observing)
  • ListMonitor: use to monitor changes to a list of NSManagedObject instances (instead of NSFetchedResultsController)

Observe a single object

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 ObjectObservers 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.

Observe a list of objects

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 to ListObserver 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 to ListObjectObserver 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 ListObservers 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

Contents