Search code examples
cocoaswiftdesign-patternscore-datansarraycontroller

group fetch / data updates (class structure design)


What would be the best design for the following scenario?

I have a class that manages a bunch of NSManagedObjects. Inserting, deleting, fetching, etc. A viewController uses this object as the dataSource for a tableView. Thus every time the managed objects change (added, deleted or altered), the tableview has to reloadData().

To ensure that my class has the correct list of objects, it should fetch() the managedObjects after every delete or insert and notify any observers that its contents have changed.

So far this is all working nicely. However I would to limit the number of fetch() operations. Like NSView only draws once even though you called setNeedsDisplay multiple times. What is the best approach to do something similar to this?

It's kind of similar to a NSArrayController, but my class performs more functions in the backend while NSArrayController is more for binding views to the backend.


Solution

  • You should look for NSManagedObjectContextObjectsDidChangeNotification, posted by NSManagedObjectContext

    The notification is posted during processPendingChanges, after the changes have been processed, but before it is safe to call save: again (if you try, you will generate an infinite loop).

    The notification object is the managed object context. The userInfo dictionary contains the following keys: NSInsertedObjectsKey, NSUpdatedObjectsKey, and NSDeletedObjectsKey.

    core data coalesce the changes for you, so it's already quite optimized.

    Anyway, depending on what you want to do, a better option could be to subclass NSArrayController, probably overriding the - (NSArray *)arrangeObjects:(NSArray *)objects method, e.g:

    - (NSArray *)arrangeObjects:(NSArray *)objects
    {
        NSArray *a1 = [self mayBeYouWantToPreprocessFetchedObjects:objects];
        NSArray *a2 = [super arrangeObjects:a1]; // this performs filtering, etc
        NSArray *a3 = [self mayBeYouWantToPostProcessArrangedObjects:a2];
        // [self doWhatYouWantWithArrangedObjects:a3]; // e.g. trigger a reloadData if you're not using bindings
        // or probably better : performOnMainThread: a method that will use arrangedObjects :
        [self performSelectorOnMainThread:@selector(dataWasReloaded) withObject:nil waitUntilDone:NO];
    
        return a3;
    }
    

    Doing so, you would get for free

    • all core data handling, including optimising the number of fetch (you can expect/hope NSArrayController is well optimised, and won't rearrange object when it's not necessary)
    • possibility to bind to model source like NSArray or NSSet in addition to core data (could be f.i. the arrangedObjects of another NSArrayController)
    • possibility to bind a NSTableView to your controller
    • all NSArrayController features, e.g. predicate filtering

    I'm using such technique to provide data source to a NSOutlineView (partly because I have some specific processing on the fetched object, and also because NSTreeController is very limited), being still able to bind a NSTableView and have a flat view of my data