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

これは、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 が提供した回答に基づいていますが、実用的な例です。

4

5 に答える 5

6

ここで少し内省することが役立つはずです。ランタイム関数は、すべてのオブジェクトのプロパティのリストを提供できます。次に、それらを使用して、そのリストに依存dirtyしているKVO を伝えることができます。これにより、プロパティのリストを手動で更新しなければならないという保守性の問題が回避されます。1 つの注意点は、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

私の最終的な解決策(例を挙げてくれた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 に答える
2

ニーズによっては少しやり過ぎかもしれませんが、CoreData は、オブジェクトの状態と変更を管理するために必要なすべてを提供します。ファイルを処理したくない場合は、メモリ ベースのデータ ストアを使用できますが、最も強力なセットアップでは SQLite を使用します。

したがって、オブジェクト (NSManagedObject に基づく) は-changedValues、最後のコミット以降に変更された属性を一覧表示するメソッドや、最後にコミットされた属性-committedValuesForKeys: nilを返すメソッドなど、いくつかの便利なメソッドを継承します。

やり過ぎかもしれませんが、車輪を再発明する必要はありません。サードパーティのライブラリを使用する必要はありません。数行のコードだけでうまく機能します。SQLite データストアを使用することを選択した場合、メモリ使用量はかなり影響を受けますが、必ずしも悪いことではありません。

Core Data は別として、KVO を使用することは、独自のスナップショット メカニズムまたは変更マネージャーを実装する方法です。

于 2012-07-09T16:17:08.517 に答える
0

すべてのプロパティが何であるかはわかりませんが、それらを「スーパークラス化」してみることができます。オブジェクトを作成してObservedObjectから、このオブジェクトのサブクラスであるすべてのオブジェクトのカスタムクラスを作成します。次に、isDirtyプロパティを設定しObservedObjectて確認するか、変更されたときにプログラムに通知を送信します。さまざまな種類のオブジェクトが多数ある場合、これは大変な作業になる可能性がありますが、ほとんど同じオブジェクトが多数ある場合は、それほど悪くないはずです。

これが実行可能な解決策であるかどうか、またはこの種の問題に対して適切な解決策が見つかるかどうかを確認することに興味があります。

于 2012-07-09T16:12:53.240 に答える
0

1つのオプションは、saveメソッドにあり、古いバージョンのmyObjectを取得して、次のようなことを行います。

if (![myOldObject isEqual:myNewObject]) {

 //perform save

}
于 2012-07-09T18:02:44.490 に答える