【问题标题】:How do you determine if an object (self) with a lot of properties has been changed?您如何确定具有很多属性的对象(自身)是否已更改?
【发布时间】:2012-07-09 01:34:56
【问题描述】:

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

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

尝试 2
观察 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
}  

我很确定这会奏效,但我认为一定有更好的方法。 :) 我还希望这是自动的,这样我就不必维护要查看的属性列表。我很容易看到在维护它时忘记将属性添加到列表中,然后试图找出为什么我的对象 sometimes 没有得到保存。

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

最终解决方案
请参阅下面的答案,了解我对此的最终解决方案。它基于 Josh Caswell 提供的答案,但只是一个工作示例。

【问题讨论】:

  • 不得不承认,我能想到的只有两种方法,一种是使用自定义 setter 方法,另一种是在写入文件,并将计算的 HASH 与存储在磁盘上的文件本身的 HASH 进行比较。好问题!
  • 是的,我考虑过散列,但由于您可以使用两个不相同的对象拥有相同的散列,因此在需要保存对象时可能会不保存该对象。诚然,这可能不会经常发生,但这会让它更难追踪!
  • 实际上这不应该是正确的(好的)哈希算法。一个好的 HASH 算法应该有很少的冲突。在只有 1 个输入位发生变化的情况下,良好的 HASH 算法将更改约 50% 的消息摘要位,并且还会考虑输入的顺序(即,如果在第 1 次运行时 a 在 b 之前通过,那么在第 2 次run b 在 a 之前通过,即使 a 和 be 没有改变,输出摘要也会不同)。
  • 是的,我明白了,但您回复的关键是“很少”冲突......
  • 我不认为你会担心的是我输入的内容。 MD5 校验和和 HASH 与 Internet 上的许多文件一起出现是有原因的!这也是它们被用作许多数字签名算法的输入的原因。

标签: objective-c ios observer-pattern


【解决方案1】:

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

我很想看看这是否是一个可行的解决方案,或者对于这类问题是否可以找到一个好的解决方案。

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

    • 一点也不矫枉过正。这正是 Core Data 旨在解决并且做得很好的问题。我并不总是 Core Data 的忠实粉丝,但对于这类问题,它正是要使用的工具。如果你正在实现名称为“save”和“isDirty”的方法,那么你就是在重新发明 Core Data,应该停止。
    • 我很想使用 Core Data(我已经在其他项目中使用过它)但这是一个多平台项目,所以我需要使用 SQLite。不过感谢您的回答!
    • @RobNapier 我希望我不必重新发明 Core Data,但由于它是一个多平台项目,它实际上在这里提供了一些灵感!
    • 多平台你使用Objective-C?我肯定不会猜到的。您的目标平台是否支持 KVO?
    • @fabricetruillotdechambrier 数据来源(SQLite3 数据库)必须是多平台和共享的。当然,我们的数据模型特定于 Objective-c 版本。
    【解决方案3】:

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

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

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

    【讨论】:

    • 嘿,酷!我计划使用这种技术单独观察所有值,但没有想到在 keyPathsForValuesAffecting 中使用它。我不明白的一件事是更新的肮脏程度。当依赖键之一更改时,这将为脏提供更改通知,但脏的实际值保持不变......当依赖键更改时,我会以某种方式需要更新脏。
    • 只需在observeValueForKeyPath:ofObject:change:context: 中执行此操作,就像您单独观察每个键一样。您对 KVO 的依赖有点撒谎,但您可以手动更新 dirty。当然,你必须在observeValue...中设置ivar
    • Sooo... 观察dirty,如果我看到它已经改变,(即使它真的没有)把它改成YES?然后,为了将其设置回 NO(在保存后或加载后立即),我想直接更改 ivar(否则它会自动将其更改回 YES,因为它已更改)。跨度>
    • +1 我喜欢。今天晚些时候我会尝试一下,看看效果如何。感谢您的意见!
    • 对,你总是必须直接处理dirty ivar。这可能有点尴尬,所以我的最后一句话。希望一切顺利。
    【解决方案4】:

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

    if (![myOldObject isEqual:myNewObject]) {
    
     //perform save
    
    }
    

    【讨论】:

    • 是的,这违背了 isDirty 的目的。我可以每次都保存它,但是按照您的方式,我必须每次都阅读它,进行比较,然后在需要时保存它。比原计划还要多的工作......
    【解决方案5】:

    我的最终解决方案(感谢 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;
        }
    }
    

    【讨论】:

    • 这个解决方案对您来说效果很好吗?这似乎是一个常见问题,我想我可以试试这个解决方案。
    • 是的,它对我来说效果很好,请记住,如果您通过其 ivar 更改属性,则不会设置 isDirty。
    猜你喜欢
    • 1970-01-01
    • 2020-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-12
    • 1970-01-01
    • 2011-03-04
    • 2015-09-20
    相关资源
    最近更新 更多