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

2

19

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?

Kostroma answered 21/11, 2012 at 10:40 Comment(0)
O
21

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.

Ouphe answered 21/11, 2012 at 11:29 Comment(2)
This seems indirect, but pointed me in the right direction. class_copyPropertyList() and property_getname() are enough to add observation on every property, exactly as originally asked.Kayekayla
So are there any updates to the code snippet above then?Wat
H
2

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)
        }
    }
}
Hauptmann answered 21/1, 2020 at 10:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.