Search code examples
iosobjective-ckey-value-observingkvocontroller

How to add KVO to synchronized class?


In my app I have the Restaurant class that you can see below. I'd like to attach a KVOController to it. But I'm having no luck. When I attach it with the code below, it crashes.

FBKVOController *KVOController = [FBKVOController controllerWithObserver:self];
    self.KVOController = KVOController;

    [self.KVOController observe:self keyPath:@"[Restaurant current].name.asString" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(id observer, id object, NSDictionary *change) {
        DDLogDebug(@"Restaurant changed");
    }];

What's the best way to add KVO to a class like this?

@implementation Restaurant

static Restaurant *current = nil;

+ (Restaurant *)current {
    @synchronized(self) {
        if (current == nil) {            
            current = [[Restaurant alloc] initWithId:0];
        }
    }
    return current;
}

- (id)initWithId:(NSInteger)number {
    self = [super init];
    if (self)
    {
        ...
    }
    return self;
}

@end

Solution

  • The problem is not @synchronized. There are several issues with your code:

    • Do you want to observe when the current restaurant changes? Or when the current restaurant's name changes (without +[Restaurant current] pointing to a different restaurant instance). Or any kind of name change, whether triggered by a change of current or a change of name?
      • Depending on the answer, you'll either want to observe observe:[Restaurant class] or observe:[Restaurant instance], but definitely not observe:self (unless you're setting this up inside the Restaurant class implementation, in which case [self class] would be an alternative to [Restaurant class]).
      • For any change to be observable, you must ensure that the class is implemented in a KVO-compliant way. This goes both for changes to +[Restaurant current] as well as for changes to -[Restaurant name], depending on what you want to be able to observe.
    • [Restaurant current].name.asString is not a valid key path. Valid key paths may only contain property names (ASCII, begin with a lowercase letter, no whitespace) and dots to separate them (see Key-value coding for details). Once you're telling the KVOController to observe:[Restaurant class], all that remains for the key path is current.name.asString.
    • What is name if not a string? Do you really need to convert it to a string for observing it? If your intention is to watch for name changes, observing current.name is probably sufficient.

    You'll likely end up with one of the following two options:

    FBKVOController *kvoController = [FBKVOController controllerWithObserver:self];
    [kvoController observe:[Restaurant class] keyPath:@"current.name" ...];`
    // or
    [kvoController observe:[Restaurant current] keyPath:@"name" ...];`
    

    And again, for any changes to be observable, they need to be KVO-compliant.