如何确定具有大量属性的对象(self)是否已更改?

lna*_*ger 17 objective-c ios observer-pattern

问题的简短版本:
我有一个具有大量声明属性的类,我想跟踪它是否有任何更改,以便当我调用save它的方法时,它不会写不需要时到数据库.如何更新的isDirty特性,而无需编写自定义的制定者对于所有声明的属性

问题的较长版本:
假设我有一个这样的类:

@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;
}
Run Code Online (Sandbox Code Playgroud)

尝试1
我的第一个想法就是这样实现setValue:forKey::

- (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];
}
Run Code Online (Sandbox Code Playgroud)

这完全有效,直到您使用setter(即myObject.myString = @"new string";)直接设置值,在这种情况下setValue:forKey:不会被调用(我不知道为什么我认为它会是,lol).

尝试2
观察自己的所有属性.

- (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
}  
Run Code Online (Sandbox Code Playgroud)

我很确定这会奏效,但我认为必须有更好的方法.:)我也希望这是自动的,这样我就不必维护要监视的属性列表.我可以很容易地看到在保持这种状态时忘记将属性添加到列表中,然后试图找出为什么我的对象有时无法保存.

希望我忽略了一个更简单的解决这个问题的方法!

最终解决方案
请参阅下面的答案以获得最终解决方案.它基于Josh Caswell提供的答案,但却是一个有效的例子.

Jos*_*ell 6

有点内省应该有帮助.在运行时函数可以给你的所有对象的属性的列表.然后,您可以用这些来告诉KVO这dirty取决于在此列.这避免了必须手动更新属性列表的可维护性问题.需要注意的是,与任何其他涉及KVO的解决方案一样,如果直接更改ivar,您将不会收到通知 - 所有访问必须通过setter方法.

注册以观察其中selfdirty关键路径init,并添加此方法,创建并返回一个NSSet包含所有类属性的名称(@"dirty"当然除外).

#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;
}
Run Code Online (Sandbox Code Playgroud)

现在dirty只要设置了这个类的属性,就会触发观察.(请注意,超类中定义的属性不包含在该列表中.)

您可以改为使用该列表分别注册为所有名称的观察者.