The short version of the question:
I have a class with a ton of declared properties, and I want to keep track of whether or not there have been any changes to it so that when I call a save
method on it, it doesn't write to the database when it isn't needed. How do I update an isDirty
property without writing custom setters for all of the declared properties?
The longer version of the question:
Let's say that I have a class like this:
@interface MyObject : NSObject
{
@property (nonatomic, retain) NSString *myString;
@property (nonatomic, assign) BOOL myBool;
// ... LOTS more properties
@property (nonatomic, assign) BOOL isDirty;
}
...
@implementation MyObject
{
@synthesize myString;
@synthesize myBool;
// ... LOTS more synthesizes :)
@synthesize isDirty;
}
Attempt 1
My first thought was to implement setValue:forKey:
like this:
- (void)setValue:(id)value forKey:(NSString *)key {
if (![key isEqualToString:@"isDirty"]) {
if ([self valueForKey:key] != value) {
if (![[self valueForKey:key] isEqual:value]) {
self.isDirty = YES;
}
}
}
[super setValue:value forKey:key];
}
This works perfectly until you set the value directly with a setter (i.e. myObject.myString = @"new string";
), in which case setValue:forKey:
isn't called (I'm not sure why I thought that it would be, lol).
Attempt 2
Observe all properties of self.
- (id)init
{
// Normal init stuff
// Start observing all properties of self
}
- (void)dealloc
{
// Stop observing all properties of self
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// set isDirty to true
}
I'm pretty sure that this will work, but I think that there must be a better way. :) I also want this to be automatic, so that I don't have to maintain a list of properties to watch. I can easily see forgetting to add a property to the list when maintaining this down the road and then trying to figure out why my object sometimes doesn't get saved.
Hopefully I'm overlooking a much simpler approach to this problem!
Final Solution
See my answer below for my final solution to this. It is based on the answer provided by Josh Caswell, but is a working example.
A little introspection should help out here. The runtime functions can give you a list of all the object's properties. You can then use those to tell KVO that dirty
is dependent on that list. This avoids the maintainability problem of having to update the list of properties by hand. The one caveat is that, like any other solution involving KVO, you won't be notified if the ivar is changed directly -- all access must be through setter methods.
Register to observe self
's dirty
key path in init
, and add this method, creating and returning an NSSet
with the names of all the class's properties (except @"dirty"
, of course).
#import <objc/runtime.h>
+ (NSSet *)keyPathsForValuesAffectingDirty
{
unsigned int num_props;
objc_property_t * prop_list;
prop_list = class_copyPropertyList(self, &num_props);
NSMutableSet * propSet = [NSMutableSet set];
for( unsigned int i = 0; i < num_props; i++ ){
NSString * propName = [NSString stringWithFormat:@"%s", property_getName(prop_list[i])];
if( [propName isEqualToString:@"dirty"] ){
continue;
}
[propSet addObject:propName];
}
free(prop_list);
return propSet;
}
Now an observation of dirty
will be triggered whenever any of this class's properties are set. (Note that properties defined in superclasses are not included in that list.)
You could instead use that list to register as an observer for all the names individually.
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