Подтвердить что ты не робот

Как вы определяете, был ли изменен объект (я) с множеством свойств?

Краткая версия вопроса:
У меня есть класс с тонны объявленных свойств, и я хочу отслеживать, были ли какие-либо изменения в нем, поэтому, когда я вызываю метод 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: не вызывается (я не уверен, почему я думал, что это будет, lol).

Попытка 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
}  

Я уверен, что это сработает, но я думаю, что должен быть лучший способ.:) Я также хочу, чтобы это было автоматически, так что мне не нужно поддерживать список свойств для просмотра. Я легко вижу, что забываю добавлять свойство в список при сохранении этого вниз по дороге, а затем пытаюсь выяснить, почему мой объект иногда не сохраняется.

Надеюсь, я упустил гораздо более простой подход к этой проблеме!

Окончательное решение
См. Мой ответ ниже для моего окончательного решения. Он основан на ответе, предоставленном Джошем Касуэлом, но является рабочим примером.

4b9b3361

Ответ 1

Небольшая интроспекция должна помочь здесь. функции времени выполнения могут предоставить вам список всех свойств объекта. Затем вы можете использовать те, чтобы сообщить KVO, что dirty dependent в этом списке. Это позволяет избежать проблемы ремонтопригодности при обновлении списка свойств вручную. Одно из предостережений заключается в том, что, как и любое другое решение, связанное с 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 будет срабатывать всякий раз, когда будет задано какое-либо из свойств этого класса. (Обратите внимание, что свойства, определенные в суперклассах, не включены в этот список.)

Вместо этого вы можете использовать этот список для регистрации в качестве наблюдателя для всех имен отдельно.

Ответ 2

В зависимости от ваших потребностей может быть немного перебор, но CoreData предоставляет все необходимое для управления состояниями и изменениями объектов. Вы можете использовать хранилище данных на основе памяти, если вы не хотите иметь дело с файлами, но самая мощная настройка использует SQLite.

Итак, ваши объекты (на основе NSManagedObject) наследуют несколько полезных методов, таких как -changedValues, в которых перечислены измененные атрибуты с момента последнего фиксации или -committedValuesForKeys: nil, который возвращает последние зафиксированные атрибуты.

Возможно, излишний, но вам не нужно изобретать колесо, вам не нужно использовать стороннюю библиотеку, и для ее работы потребуется всего несколько строк кода. Использование памяти будет иметь довольно хороший бит, но не обязательно для плохого, если вы решите использовать хранилище данных SQLite.

Разделять Core Data, используя KVO, - это способ реализовать свой собственный механизм моментального снимка или менеджер изменений.

Ответ 3

Мое окончательное решение (спасибо 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;
    }
}

Ответ 4

Я не знаю, что все ваши свойства, но вы можете попробовать их "суперклассирование". Создайте объект ObservedObject, а затем создайте пользовательские классы для всех ваших объектов, которые являются подклассами этого объекта. Затем либо поместите свойство isDirty на ObservedObject, либо посмотрите на него или отправьте уведомление в вашу программу, когда оно будет изменено. Это может быть много работы, если у вас много разных типов объектов, но если у вас в основном много одного и того же объекта, это не должно быть слишком плохо.

Мне интересно узнать, является ли это жизнеспособным решением или может быть найдено хорошее решение для такого рода проблем.

Ответ 5

Один из вариантов будет в вашем методе сохранения, чтобы получить старую версию myObject и сделать что-то вроде

if (![myOldObject isEqual:myNewObject]) {

 //perform save

}