你如何确定一个有很多属性的对象(自我)是否被改变了?
问题的简短版本:
我有一个声明属性的类,我想跟踪是否有任何更改,以便当我调用一个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
观察自我的所有属性。
- (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 }
我很确定这会起作用,但我认为必须有更好的方法。 :)我也希望这是自动的,所以我不必维护一个属性列表来观看。 我可以很容易地看到忘记添加一个属性到列表中时,保持这个道路,然后试图找出为什么我的对象有时不能得到保存。
希望我可以忽略一个更简单的方法来解决这个问题!
最终解决scheme
看到我的答案下面我最后的解决scheme。 这是基于乔什·卡斯韦尔提供的答案,但是是一个工作的例子。
有点内省应该在这里帮忙。 运行时函数可以给你一个所有对象属性的列表。 然后,您可以使用这些来告诉KVO dirty
是依赖于该列表。 这避免了必须手动更新属性列表的可维护性问题。 有一点需要注意的是,就像任何涉及KVO的解决scheme一样,如果直接更换伊娃,您将不会得到通知 – 所有访问都必须通过设置方法。
注册以观察self
在init
的dirty
关键path,并添加此方法,创build并返回一个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
的观察。 (请注意,在超类中定义的属性不包含在该列表中。)
您可以改为使用该列表单独注册为所有名称的观察者。
根据您的需要,这可能有点矫枉过正,但CoreData提供了pipe理对象状态和更改所需的一切。 如果您不想处理文件,则可以使用基于内存的数据存储,但最强大的设置使用SQLite。
那么,你的对象(基于NSManagedObject)将会inheritance一些有用的方法,比如-changedValues
,它列出了自上次提交以来更改的属性或-committedValuesForKeys: nil
,它返回最后提交的属性。
过度杀伤可能,但是你不必重新发明轮子,你不需要使用第三方库,只需要几行代码就可以很好地工作。 内存使用情况将受到相当程度的影响,但如果您select使用SQLite数据存储,则不一定会造成不良影响。
除了核心数据,使用KVO是实现自己的快照机制或更改pipe理器的方式。
我的最终解决scheme(感谢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; } }
我不知道你的所有属性是什么,但你可以尝试“超类”。 创build一个ObservedObject
对象,然后为这个对象的所有子对象创build自定义类。 然后,在ObservedObject
上放置一个isDirty
属性并查看它,或者在更改时向程序发送通知。 如果你有很多不同types的对象,这可能是很多工作,但如果你有大部分相同的对象,它应该不会太糟糕。
我很感兴趣,看看这是一个可行的解决scheme,还是可以find这样的问题的一个好的解决scheme。
一种select是在你的保存方法中获得旧版本的myObject并做类似的事情
if (![myOldObject isEqual:myNewObject]) { //perform save }