Key Value Observing - how to observe all the properties of an object?
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.
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)
}
}
}