lostintranslation
lostintranslation

Reputation: 24563

Detect changes to single coredata entity

I have the following coredata entities:

@interface Car (CoreDataProperties)

@property (nullable, nonatomic, retain) NSSet<Part *> *parts;

@end

@interface Part (CoreDataProperties)

@property (nullable, nonatomic, retain) Car *car;

@end

This is a one to many relationship between car and parts. In one of my view controllers I display a car view with all parts. I want to listen for changes to the parts of the car.

I thought this would be easy enough with KVO.

- (void) viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    [self.car addObserver:self forKeyPath:@"parts" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
}

then:

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([@"parts" isEqualToString:keyPath]) {
        // do what I need to do with new parts
    }
}

However when I pull changes from a server to update a car parts I am getting unexpected callbacks to the observeValueForKeyPath method even when I do not change any parts.

Wondering if faulting could be the problem: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/FaultingandUniquing.html#//apple_ref/doc/uid/TP40001075-CH18-SW7

Was hoping to use KVO, but maybe with coredata objects its just not a great idea. Am I doing something wrong or should I use an alternative?

If I should not be using KVO, I believe my other options are: 1. Listen for MOC changes. This is not great as I don't really know what changed on the object. 2. Implement a FetchedResultsController on parts with a predicate to find only the car I am interested in. Seems like a bit of overkill, but I think this will give me what I need.

Upvotes: 1

Views: 5634

Answers (1)

bteapot
bteapot

Reputation: 2027

NSFetchedResultsController is the preferred way to observe changes in some data subset.

And for single object you can observe changes in NSManagedObjectContext.

Subscribe to NSManagedObjectContextObjectsDidChangeNotification:

[[NSNotificationCenter defaultCenter]
    addObserver:self
       selector:@selector(contextDidChange:)
           name:NSManagedObjectContextObjectsDidChangeNotification
         object:context];

When notification arrives, process its userInfo:

- (void)contextDidChange:(NSNotification *)notification
{
    NSManagedObjectContext *context = notification.object;
    NSDictionary *userInfo = notification.userInfo;

    NSArray *invalidatedAll = userInfo[NSInvalidatedAllObjectsKey];
    NSSet *invalidated      = userInfo[NSInvalidatedObjectsKey];
    NSSet *deleted          = userInfo[NSDeletedObjectsKey];
    NSSet *updated          = userInfo[NSUpdatedObjectsKey];
    NSSet *refreshed        = userInfo[NSRefreshedObjectsKey];

    // context reset
    if (invalidatedAll) {
        // probably you better have to dismiss your VC here.
        return;
    }

    // invalidated
    if ([invalidated containsObject:self.car]) {
        // it make sense to dismiss here too.
        return;
    }

    // deleted
    if ([deleted containsObject:self.car]) {
        // and here.
        return;
    }

    // refreshed
    if ([refreshed containsObject:self.car]) {
        // update your interface.
        return;
    }

    // updated
    if ([updated containsObject:self.car]) {
        // update your interface.
        return;
    }
}

To receive notifications about Car object when related Parts updated, see this answer.

You can get list of changed attributes from changedValues property.

Don't forget to unsubscribe:

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter]
        removeObserver:self
                  name:NSManagedObjectContextObjectsDidChangeNotification
                object:context];
}

UPDATE:

Here's that conception implemented as a UIViewController category:
BTDependentVC

Upvotes: 5

Related Questions