问题的简短版本:
我有一个具有大量声明属性的类,我想跟踪它是否有任何更改,以便当我在其上调用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提供的答案,但是是一个有效的例子。
答案 0 :(得分:6)
有点内省应该有帮助。 runtime functions可以为您提供所有对象属性的列表。然后,您可以使用这些来告诉KVO该列表中dirty
是dependent。这避免了必须手动更新属性列表的可维护性问题。需要注意的是,与任何其他涉及KVO的解决方案一样,如果直接更改ivar,您将不会收到通知 - 所有访问必须通过setter方法。
注册以观察self
中dirty
的{{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
}