Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

KVO for whole object properties

Is it possible to add an observer to get notified if any of the monitored object properties is modified? For example:

@interface OtherObject : NSObject

@property (nonatomic) MyObject* myObject;

@end

and

@interface MyObject : NSObject

@property (nonatomic) unsigned int property1;
@property (nonatomic) unsigned int property2;

@end

I would like to do something like:

[otherObject addObserver:self
               forKeyPath:@"myObject"
                  options:0
                  context:nil]

and get notified if either property1 or property2 is modified. It does not seem to work if I register the holding object (somehow makes sense because myObject is not really modified when I modify property1 for example).

like image 385
Resh32 Avatar asked Oct 01 '12 12:10

Resh32


People also ask

What is KVO used for?

KVO, which stands for Key-Value Observing, is one of the techniques for observing the program state changes available in Objective-C and Swift. The concept is simple: when we have an object with some instance variables, KVO allows other objects to establish surveillance on changes for any of those instance variables.

What is Swift KVO?

Key-value observing is a mechanism that enables an object to be notified directly when a property of another object changes. It is a mode of communication between objects in applications designed in conformance with the Model-View-Controller design pattern.

What are Key-Value coding and Key-Value observing?

KVO and KVC or Key-Value Observing and Key-Value Coding are mechanisms originally built and provided by Objective-C that allows us to locate and interact with the underlying properties of a class that inherits NSObject at runtime.

What is key-value observing?

Key-value observing is a Cocoa programming pattern you use to notify objects about changes to properties of other objects. It's useful for communicating changes between logically separated parts of your app—such as between models and views. You can only use key-value observing with classes that inherit from NSObject .


3 Answers

I can think of two options.

  • You could create a separate "master" property and make it depend on all your other properties.

    @interface MyObject : NSObject
    @property (nonatomic) id masterProperty;
    @property (nonatomic) unsigned int property1;
    @property (nonatomic) unsigned int property2;
    @end
    

    + (NSSet *)keyPathsForValuesAffectingMasterProperty {
        return [NSSet setWithObjects:@"property1", @"property2", nil];
    }
    

    If you observe masterProperty you'll be notified when any of the properties change.

  • You use the Objective-C runtime to get a list of all the properties and observe them all.

    - (void)addObserverForAllProperties:(NSObject *)observer
                                options:(NSKeyValueObservingOptions)options
                                context:(void *)context {
        unsigned int count;
        objc_property_t *properties = class_copyPropertyList([self class], &count);
        for (size_t i = 0; i < count; ++i) {
            NSString *key = [NSString stringWithCString:property_getName(properties[i])];
            [self addObserver:observer forKeyPath:key 
                      options:options context:context];
        }
        free(properties);
    }
    
like image 83
DrummerB Avatar answered Oct 10 '22 00:10

DrummerB


What you could do is have a function to modify a particular property of the myObject...

-(void)setMyObjectName:(NSString*)name;

and then in the function have this code...

- (void)setMyObjectName:(NSString*)name
{
    [self willChangeValueForKey:@"myObject"];

    myObject.name = name;

    [self didChangeValueForKey:@"myObject"];
}

This will then notify the observer when that property on myObject is changed.

Whever you need this doing use this pattern and you can get notified for any change on myObject.

::EDIT:: Having said that, you should be able to use...

[otherObject addObserver:self
              forKeyPath:@"myObject.property1"
                 options:0
                 context:nil];

and that will observe property1 and do the same fort the other properties.

But this will mean adding an observer for each property individually.

like image 40
Fogmeister Avatar answered Oct 10 '22 01:10

Fogmeister


Swift version of DrummerB answer, with an additional function to remove the observations (eg in deinit):

extension NSObject {
    func addObserverForAllProperties(
        observer: NSObject,
        options: NSKeyValueObservingOptions = [],
        context: UnsafeMutableRawPointer? = nil
    ) {
        performForAllKeyPaths { keyPath in
            addObserver(observer, forKeyPath: keyPath, options: options, context: context)
        }
    }

    func removeObserverForAllProperties(
        observer: NSObject,
        context: UnsafeMutableRawPointer? = nil
    ) {
        performForAllKeyPaths { keyPath in
            removeObserver(observer, forKeyPath: keyPath, context: context)
        }
    }

    func performForAllKeyPaths(_ action: (String) -> Void) {
        var count: UInt32 = 0
        guard let properties = class_copyPropertyList(object_getClass(self), &count) else { return }
        defer { free(properties) }
        for i in 0 ..< Int(count) {
            let keyPath = String(cString: property_getName(properties[i]))
            action(keyPath)
        }
    }
}
like image 41
Yonat Avatar answered Oct 10 '22 02:10

Yonat