objective-c ios observer-pattern

objective c - ¿Cómo determinas si un objeto(yo) con muchas propiedades ha sido cambiado?



objective-c ios (5)

La versión corta de la pregunta:
Tengo una clase con un montón de propiedades declaradas, y quiero hacer un seguimiento de si ha habido algún cambio o no para que cuando llame a un método de save , no escriba en la base de datos cuando esté no es necesario ¿Cómo actualizo una propiedad isDirty sin escribir isDirty personalizados para todas las propiedades declaradas ?

La versión más larga de la pregunta:
Digamos que tengo una clase como esta:

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

Intento 1
Mi primer pensamiento fue implementar setValue:forKey: así:

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

Esto funciona perfectamente hasta que establece el valor directamente con un setter (es decir, myObject.myString = @"new string"; ), en cuyo caso setValue:forKey: no se llama (no estoy seguro de por qué pensé que sería , jajaja).

Intento 2
Observar todas las propiedades del yo.

- (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 }

Estoy bastante seguro de que esto funcionará, pero creo que debe haber una mejor manera. :) También quiero que esto sea automático, para que no tenga que mantener una lista de propiedades para ver. Puedo ver fácilmente olvidando agregar una propiedad a la lista al mantener esto en el camino y luego tratar de averiguar por qué mi objeto a veces no se guarda.

¡Espero que esté pasando por alto un enfoque mucho más simple para este problema!

Solución final
Vea mi respuesta a continuación para mi solución final a esto. Se basa en la respuesta proporcionada por Josh Caswell, pero es un ejemplo práctico.


Mi solución final (¡Gracias Josh Caswell por el ejemplo!):

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


No sé cuáles son todas sus propiedades, pero podría intentar "superclasificarlas". Cree un objeto ObservedObject y luego cree clases personalizadas para todos sus objetos que son subclases de este objeto. Luego, ponga una propiedad isDirty en ObservedObject y mire, o envíe una notificación a su programa cuando se modifique. Esto puede ser una gran cantidad de trabajo si tiene muchos tipos diferentes de objetos, pero si tiene casi todo el mismo objeto, no debería ser tan malo.

Estoy interesado en ver si esta es una solución viable o si se puede encontrar una buena solución para este tipo de problema.


Puede ser un poco excesivo dependiendo de sus necesidades, pero CoreData proporciona todo lo necesario para administrar los estados y cambios de los objetos. Puede utilizar un almacén de datos basado en la memoria si no desea tratar con archivos, pero la configuración más poderosa usa SQLite.

Entonces, sus objetos (basados ​​en NSManagedObject) heredarán un puñado de métodos útiles, como -changedValues que enumera los atributos modificados desde la última confirmación o -committedValuesForKeys: nil que devuelve los últimos atributos confirmados.

Posiblemente exageres, pero no tienes que reinventar la rueda, no necesitas usar una biblioteca de terceros, y solo necesitarás unas pocas líneas de código para que funcione bien. El uso de la memoria se verá afectado bastante, pero no necesariamente para el mal si elige usar un almacén de datos SQLite.

Aparte de los datos básicos, usar KVO es el camino a seguir para implementar su propio mecanismo de instantáneas o administrador de cambios.


Un poco de introspección debería ayudar aquí. Las funciones de tiempo de ejecución pueden darle una lista de todas las propiedades del objeto. Luego puede usarlos para decirle a KVO que el dirty dependent de esa lista. Esto evita el problema de mantenibilidad de tener que actualizar la lista de propiedades a mano. La única advertencia es que, al igual que con cualquier otra solución que involucre KVO, no se le notificará si el ivar se cambia directamente; todo el acceso debe realizarse a través de métodos de establecimiento.

Regístrese para observar self ruta de la clave dirty en init , y agregue este método, creando y devolviendo un NSSet con los nombres de todas las propiedades de la clase (excepto @"dirty" , por supuesto).

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

Ahora se activará una observación de dirty cada vez que se establezca alguna de las propiedades de esta clase. (Tenga en cuenta que las propiedades definidas en las superclases no están incluidas en esa lista).

En su lugar, podría usar esa lista para registrarse como observador para todos los nombres individualmente.


Una opción estaría en su método de guardar para obtener la versión anterior de myObject y hacer algo como

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