Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do something on each property change

I have several properties in my class, I would like to call saveToFile on each property change.

I prefer not to override the setter of each property. Should I override -[NSObject methodForSelector]? What is the best way to go?

like image 523
Kamen Dobrev Avatar asked May 13 '15 06:05

Kamen Dobrev


2 Answers

You can register as observer to the properties you want monitored. Cocoa's KVO functionality will help you here.

Basically you need to call addObserver:forKeyPath:options:context: and register to be notified when the properties change. When this happens, the runtime calls the observeValueForKeyPath:ofObject:change:context: method on the object registered as observer. You can do here the saving you want to do.

Example for registering:

for(NSString *propName in self.propsIWantMonitored) {
    [self addObserver:self forKeyPath:propName change:0 context:@selector(saveToFile)];
}

and for dealing with the change of the prop values:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{

    // make sure we don't interfere with other observed props
    // and check the context param
    if (context == @selector(saveToFile)) {
        [self saveToFile];
    }
}

and for de-registering:

for(NSString *propName in self.propsIWantMonitored) {
    [self removeObserver:self forKeyPath:propName context:@selector(saveToFile)];
}

The code samples above assume you have declared an array of properties to monitor, that you use to register as observer to. You use the context parameter to determine if observeValueForKeyPath was called as a response to the observer you just registered, or not, in order not to get into conflict with other KVO registrations made from other parts of your class.


Alternative (and more energy efficient) approach to your problem

There's one caveat with the above approach: if multiple properties are set consecutively, then the saveToFile method will be called multiple times in a short period of time, which might cause performance bottlenecks and increase the energy usage of your application.

An alternative approach would be to have a dirty flag that gets set in observeValueForKeyPath: and gets reset in saveToFile. And you can have saveToFile check the flag and don't go use the file system if the object is not dirty.

You can schedule a timer that will periodically call saveToFile, this way multiple properties set at once will result in only one disk access. You can always manually call saveToFile when you feel want an immediate save.

Note. By timer I was referring to a GCD timer, as NSTimer also has a negative energy impact on your application.

like image 138
Cristik Avatar answered Oct 18 '22 19:10

Cristik


What you want is called Key-Value-Observing or KVO. You register for example a method that gets called every time the property changes.

If you have a text field and you want to listen to changes to its text, you would register like this

[self.textField addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];

And in your class you would implement this method:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{

    if ([keyPath isEqualToString:@"text"]) {

        NSLog(@"Textfield changed - MAKE CHANGES HERE");
    }

}

Here's a nice tutorial, if you aren't familiar with KVO: http://www.appcoda.com/understanding-key-value-observing-coding/

like image 32
Alexander Gattringer Avatar answered Oct 18 '22 19:10

Alexander Gattringer