Search code examples
key-value-observingreactive-cocoa

Receive KVO notifications for all property and changes recursively at top level


If I have the following data model:

Model
    - subModels (NSMutableArray * of type SubModel)

SubModel
    - text
    - createdAt

is it possible to use KVO to observe changes on the parent Model class and still receive changes notifications for properties on relationship models?

Using ReactiveCocoa, what I'm trying to accomplish would look something like this:

self.model = [Model new];
self.model.subModels = [NSMutableArray array];
SubModel *subModelOne = [SubModel new];
[self.model mutableArrayValueForKeyPath:@"subModels"] addObject:subModelOne];
[RACObserve(self, model) subscribeNext:^(id next){
     NSLog(@"%@", next);
}];
subModelOne.text = @"Lollipop, lollipop, oh lolli lolli lolli lollipop!";

What I want to happen is I would get a next event from initializing model.subModels to an empty array, one from adding a sub model to the relationship, and finally one from setting subModelOne.text. Essentially I want all subproperties, submodels, etc, KVO notifications to propagate up the chain but I'm not sure how to accomplish that task.


Solution

  • The last paragraph in your question is the key to the solution:

    What I want to happen is I would get a next event from initializing model.subModels to an empty array...

    So we'll have one signal watching the subModels property on model.

    one from adding a sub model to the relationship,

    Not sure if you can do this specifically. With KVO, when you observe a property you're observing changes to that property, so when you observe a property that is an NSMutableArray object, you won't receive notifications until you change the object, by which I mean: assign a new array to that property. Adding or removing items from the array don't count as "changing the property". So the ideal way to do this would be to use an immutable array, when you need to change the items, create a new NSArray as needed and assign it to the property. Then you can observe it. You're using the mutableArrayValueForKey:, which apparently makes the count property KVO-compliant. I've never used it so I'm not sure atm. Which option to go with is up to you.

    and finally one from setting subModelOne.text.

    Our last signal will be watching the text property of the subModel.

    If you want a single signal that sends you events for all these changes, you use combineLatest:reduce::

    RACSignal* subModelsSignal = [RACObserve(self.model, subModels) startWith:self.model.subModels];
    RACSignal* textSignal = [RACObserve(subModel, text) startWith:subModel.text];
    ...
    NSArray* allSignalsYouWantCombined = @[subModelsSignal, textSignal, ... ];
    RACSignal* combined = [RACSignal combineLatest:allSignalsYouWantCombined reduce:^(NSArray* subModels, text, ...) { ... }];