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

时间:2012-07-09 15:51:29

标签: objective-c ios observer-pattern

问题的简短版本:
我有一个具有大量声明属性的类,我想跟踪它是否有任何更改,以便当我在其上调用save方法时,它不会写入数据库不需要时。如何更新isDirty属性而不为所有声明的属性编写自定义setter

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

@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;
}

尝试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];
}

这直到您使用setter(即myObject.myString = @"new string";)直接设置值才能正常工作,在这种情况下setValue:forKey:未被调用(我不确定为什么我认为它会是,笑)。

尝试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
}  

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

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

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

5 个答案:

答案 0 :(得分:6)

有点内省应该有帮助。 runtime functions可以为您提供所有对象属性的列表。然后,您可以使用这些来告诉KVO该列表中dirtydependent。这避免了必须手动更新属性列表的可维护性问题。需要注意的是,与任何其他涉及KVO的解决方案一样,如果直接更改ivar,您将不会收到通知 - 所有访问必须通过setter方法。

注册以观察selfdirty的{​​{1}}关键路径,并添加此方法,创建并返回包含所有类属性名称的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; } 的观察。 (请注意,超类中定义的属性不包含在该列表中。)

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

答案 1 :(得分:2)

根据您的需要,它可能有点过分,但CoreData提供了管理对象状态和更改所需的一切。如果您不想处理文件,可以使用基于内存的数据存储,但最强大的设置使用SQLite。

那么,你的对象(基于NSManagedObject)将继承一些有用的方法,比如-changedValues列出自上次提交以来更改的属性或-committedValuesForKeys: nil返回最后提交的属性。< / p>

可能过度杀伤,但你不必重新发明轮子,你不需要使用第三方库,它只需要几行代码就可以很好地工作。如果您选择使用SQLite数据存储区,内存使用率将受到相当大的影响,但不一定是坏的。

除了核心数据之外,使用KVO是实现自己的快照机制或更改管理器的方法。

答案 2 :(得分:2)

我的最终解决方案(感谢Josh Caswell的榜样!):

- (id)init
{
    if (self = [super init])
    {
        [self addObserver:self forKeyPath:@"isDirty" options:0 context:NULL];
    }
    return self;
}

- (void)dealloc
{
    [self removeObserver:self forKeyPath:@"isDirty"];
}

- (BOOL)loadData
{
    // Load the data, then if successful:
    isDirty = NO;
    return YES;
}

- (BOOL)saveData
{
    if (!self.isDirty)
    {
        return YES;
    }

    // Save the data, then if successful:
    isDirty = NO;
    return YES;
}

// isDirty is dependant on ALL of our declared property.
+ (NSSet *)keyPathsForValuesAffectingIsDirty
{
    unsigned int    num_props;
    objc_property_t *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:@"isDirty"] )
        {
            [propSet addObject:propName];
        }
    }

    free(prop_list);

    return propSet;
}

// If any of our declared properties are changed, this will be called so set isDirty to true.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"isDirty"])
    {
        isDirty = YES;
    }
}

答案 3 :(得分:0)

我不知道您的所有属性是什么,但您可以尝试“超级”它们。创建一个对象ObservedObject,然后为作为该对象的子类的所有对象创建自定义类。然后在isDirty上放置一个ObservedObject属性并查看它,或者在程序发生变化时向您的程序发送通知。如果您有许多不同类型的对象,这可能会有很多工作,但如果您拥有大部分相同的对象,那么它应该不会太糟糕。

我很想知道这是否是一个可行的解决方案,或者是否可以找到解决此类问题的良好解决方案。

答案 4 :(得分:0)

一个选项是在你的save方法中获取旧版本myObject并执行类似

的操作
if (![myOldObject isEqual:myNewObject]) {

 //perform save

}