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).
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.
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.
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.
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 .
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);
}
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.
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)
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With