18

问题的简短版本:
我有一个带有大量声明属性的类,我想跟踪它是否有任何更改,以便当我调用它的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;
}

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

这可以完美地工作,直到您直接使用设置器(即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 提供的答案,但它是一个工作示例。

4

5 回答 5

6

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

注册以观察self中的dirty关键路径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;
}

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

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

于 2012-07-09T17:55:09.740 回答
2

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

因此,您的对象(基于 NSManagedObject)将继承一些有用的方法,例如-changedValues列出自上次提交以来更改的属性或-committedValuesForKeys: nil返回上次提交的属性。

可能会矫枉过正,但您不必重新发明轮子,您不需要使用第三方库,并且只需要几行代码即可使其正常工作。内存使用会受到相当大的影响,但如果您选择使用 SQLite 数据存储,则不一定是坏事。

除了 Core Data,使用 KVO 是实现自己的快照机制或变更管理器的方法。

于 2012-07-09T16:17:08.517 回答
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;
    }
}
于 2012-07-31T19:08:35.710 回答
0

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

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

于 2012-07-09T16:12:53.240 回答
0

一种选择是在您的保存方法中获取旧版本 myObject 并执行类似的操作

if (![myOldObject isEqual:myNewObject]) {

 //perform save

}
于 2012-07-09T18:02:44.490 回答