Observing a change in any class property in Objective-C

Simply put, is there a way to get a general notification when a property in the Objective-C class changes? I know that I can use KVO to control certain property changes, but I need to call a specific method whenever the setProperty: message setProperty: sent to my class. I want to be able to receive a general notice without any concern as to which property has been changed.

If this helps to find out why I want to do this, I use the quick table scroll code, which can be found here: http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-uitableview/

Part of the process of doing this is that whenever a property in a table view cell changes, you need to call [ self setNeedsDisplay ] . I would prefer not to override the setter methods for each property in my class, just to make this call.

+6
properties objective-c notifications key-value-observing
source share
4 answers

As Chuck points out, you can create a dependent key, or, of course, you can directly observe all the properties (which is less work than overloading the setters).

Using the Objective-C runtime, if you use properties exclusively, you can automate this process using class_copyPropertyList() . But I would probably do this only if this problem is a bit for you. If you have only one instance of this problem, it is probably simpler, safer, and more convenient to maintain only to directly observe the list of properties if you do not want to work in the ObjC runtime.

+5
source share

Here's an example based on suggestions from Chuck and Rob:

DrakeObject.h

 @interface DrakeObject : NSObject @property (nonatomic, strong) NSNumber *age; @property (nonatomic, strong) NSNumber *money; @property (nonatomic, strong) NSString *startPosition; @property (nonatomic, strong) NSString *currentPosition; @property (nonatomic, strong, readonly) id propertiesChanged; @end 

DrakeObject.m

 @implementation DrakeObject - (instancetype)init { self = [super init]; if (self) { self.age = @25; self.money = @25000000; self.startPosition = @"bottom"; self.currentPosition = @"here"; } return self; } - (id)propertiesChanged { return nil; } +(NSSet *)keyPathsForValuesAffectingPropertiesChanged { return [NSSet setWithObjects:@"age", @"money", @"startPosition", @"currentPosition", nil]; } 

observing propertiesChanged will let us know at any time when the property has changed.

 [self.drakeObject addObserver:self forKeyPath:@"propertiesChanged" options:NSKeyValueObservingOptionNew context:nil]; 
+4
source share
+2
source share

Here is a sample code. I have a shared object and a dother object. Dother object must maintain its state when each property changes.

 #import <Foundation/Foundation.h> @interface GeneralObject : NSObject + (instancetype)instanceWithDictionary:(NSDictionary *)aDictionary; - (instancetype)initWithDictionary:(NSDictionary *)aDictionary; - (NSDictionary *)dictionaryValue; - (NSArray *)allPropertyNames; @end 

implementation

 #import "GeneralObject.h" #import <objc/runtime.h> @implementation GeneralObject #pragma mark - Public + (instancetype)instanceWithDictionary:(NSDictionary *)aDictionary { return [[self alloc] initWithDictionary:aDictionary]; } - (instancetype)initWithDictionary:(NSDictionary *)aDictionary { aDictionary = [aDictionary clean]; for (NSString* propName in [self allPropertyNames]) { [self setValue:aDictionary[propName] forKey:propName]; } return self; } - (NSDictionary *)dictionaryValue { NSMutableDictionary *result = [NSMutableDictionary dictionary]; NSArray *propertyNames = [self allPropertyNames]; id object; for (NSString *key in propertyNames) { object = [self valueForKey:key]; if (object) { [result setObject:object forKey:key]; } } return result; } - (NSArray *)allPropertyNames { unsigned count; objc_property_t *properties = class_copyPropertyList([self class], &count); NSMutableArray *array = [NSMutableArray array]; unsigned i; for (i = 0; i < count; i++) { objc_property_t property = properties[i]; NSString *name = [NSString stringWithUTF8String:property_getName(property)]; [array addObject:name]; } free(properties); return array; } @end 

and in the end we have a dother class, which should maintain its state on every change of any property

 #import "GeneralObject.h" extern NSString *const kUserDefaultsUserKey; @interface DotherObject : GeneralObject @property (strong, nonatomic) NSString *firstName; @property (strong, nonatomic) NSString *lastName; @property (strong, nonatomic) NSString *email; @end 

and implementation

 #import "DotherObject.h" NSString *const kUserDefaultsUserKey = @"CurrentUserKey"; @implementation DotherObject - (instancetype)initWithDictionary:(NSDictionary *)dictionary { if (self = [super initWithDictionary:dictionary]) { for (NSString *key in [self allPropertyNames]) { [self addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew context:nil]; } } return self; } - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context { NSDictionary *dict = [self dictionaryValue]; [[NSUserDefaults standardUserDefaults] setObject:dict forKey:kUserDefaultsUserKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (NSString *)description { return [NSString stringWithFormat:@"%@; dict:\n%@", [super description], [self dictionaryValue]]; } @end 

Happy coding!

0
source share

All Articles