ColinE
ColinE

Reputation: 70122

Key Value Observing - how to observe all the properties of an object?

I am happy with the use of Key Value Observing (KVO), and how to register to receive notifications of property change:

[account addObserver:inspector
          forKeyPath:@"openingBalance"
             options:NSKeyValueObservingOptionNew
              context:NULL];

However, if I want to observe changes in all the properties of the account object, how can I achieve this? Do I have to register for notification for each and every property?

Upvotes: 19

Views: 6384

Answers (2)

Yonat
Yonat

Reputation: 4608

The following Swift code adds observations for each property, as suggested by david van brink. It has 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)
        }
    }
}

Upvotes: 2

Vladimir
Vladimir

Reputation: 170819

It seems there's no built-in function to subscribe for changes in all properties of the objects.

If you don't care about which exactly property has changed and can change your class you can add dummy property to it to observe changes in other properties (using + keyPathsForValuesAffectingValueForKey or +keyPathsForValuesAffecting<Key> method):

// .h. We don't care about the value of this property, it will be used only for KVO forwarding
@property (nonatomic) int dummy;

#import <objc/runtime.h>
//.m
+ (NSSet*) keyPathsForValuesAffectingDummy{

    NSMutableSet *result = [NSMutableSet set];

    unsigned int count;
    objc_property_t *props = class_copyPropertyList([self class], &count);

    for (int i = 0; i < count; ++i){
        const char *propName = property_getName(props[i]);
        // Make sure "dummy" property does not affect itself
        if (strcmp(propName, "dummy"))
            [result addObject:[NSString stringWithUTF8String:propName]];
    }

    free(props);
    return result;
}

Now if you observe dummy property you'll get KVO notification each time any of object's properties is changed.

Also you can get list of all properties in the object as in the code posted and subscribe for KVO notifications for each of them in a loop (so you don't have to hard code property values) - this way you'll get changed property name if you need it.

Upvotes: 21

Related Questions