2011-02-02 16 views
21

Tengo un atributo modificationDate en mi Entity A. Quiero establecer su valor siempre que NSManagedObject se guarde. Sin embargo, si trato de hacer eso en NSManagedObjectwillSave: método, me sale un error:Core-Data willSave: método

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to process pending changes before save. The context is still dirty after 100 attempts. Typically this recursive dirtying is caused by a bad validation method, -willSave, or notification handler.' *** 

Por lo tanto, me pregunto, ¿cuál es la mejor manera de establecer el valor de modificationDate?

Respuesta

32

De los documentos NSManagedObject para willSave:

If you want to update a persistent property value, you should typically test for equality of any new value with the existing value before making a change. If you change property values using standard accessor methods, Core Data will observe the resultant change notification and so invoke willSave again before saving the object’s managed object context. If you continue to modify a value in willSave, willSave will continue to be called until your program crashes.

For example, if you set a last-modified timestamp, you should check whether either you previously set it in the same save operation, or that the existing timestamp is not less than a small delta from the current time. Typically it’s better to calculate the timestamp once for all the objects being saved (for example, in response to an NSManagedObjectContextWillSaveNotification).

Así que tal vez algo en la línea de:

-(void)willSave { 
    NSDate *now = [NSDate date]; 
    if (self.modificationDate == nil || [now timeIntervalSinceDate:self.modificationDate] > 1.0) { 
     self.modificationDate = now; 
    } 
} 

Donde se puede ajustar el 1.0 para reflejar el delta mínima entre su esperada Save solicitudes.

+0

es necesario comprobar también si modificationDate está en el chang edValues ​​ – malhal

47

De hecho, el apple docs (que son solo la mitad de leídos en la respuesta aceptada) no recomiendan este método. Explican explícitamente que debe usar NSManagedObjectContextWillSaveNotification. Un ejemplo podría ser:

@interface TrackedEntity : NSManagedObject 
@property (nonatomic, retain) NSDate* lastModified; 
@end 

@implementation TrackedEntity 
@dynamic lastModified; 

+ (void) load { 
    @autoreleasepool { 
     [[NSNotificationCenter defaultCenter] addObserver: (id)[self class] 
               selector: @selector(objectContextWillSave:) 
                name: NSManagedObjectContextWillSaveNotification 
                object: nil]; 
    } 
} 

+ (void) objectContextWillSave: (NSNotification*) notification { 
    NSManagedObjectContext* context = [notification object]; 
    NSSet* allModified = [context.insertedObjects setByAddingObjectsFromSet: context.updatedObjects]; 
    NSPredicate* predicate = [NSPredicate predicateWithFormat: @"self isKindOfClass: %@", [self class]]; 
    NSSet* modifiable = [allModified filteredSetUsingPredicate: predicate]; 
    [modifiable makeObjectsPerformSelector: @selector(setLastModified:) withObject: [NSDate date]]; 
} 
@end 

utilizo este (con algunos otros métodos: clave primaria, por ejemplo) como una clase base abstracta para la mayoría de los proyectos de datos básicos.

+0

Me gusta! Solo buscaba algo como esto: aprecie el código de muestra. ¿Debo llamar [cargar súper]?(Sospechando que no, si NSManagedObject o su padre no hace nada con él) –

+1

¡OK! Intenté esto, y parece ser demasiado entusiasta (o más probablemente estoy abusando de él). Tengo un objeto administrado que utiliza lastModified (a través de MYAPPTrackedManagedObject, básicamente TrackedEntity anterior). Sin embargo, este objeto contiene relaciones con otros objetos que _no_ utilizan lastModified. Por lo tanto, setLastModified: no será reconocido por esos otros objetos, y se lanzará una excepción. Tal vez tengo que volver a marcarlo de alguna manera? –

+0

Ahh, tal vez simplemente camine allModified y filtre todo lo que no responda a setLastModified :, y use eso. –

9

realidad una forma mucho mejor que la respuesta aceptada sería el uso de métodos de acceso primitivas, como se sugiere en NSManagedObject's Documentation

`

- (void)willSave 
{ 
    if (![self isDeleted]) 
    { 
     [self setPrimitiveValue:[NSDate date] forKey:@"updatedAt"]; 
    } 
    [super willSave]; 
} 

`

Además, verifique si el objeto está marcado para eliminación con -isDeleted, como -willSave se llama para aquellos también.

+8

Los documentos mencionan esto sobre anulación willSave: "Si cambia los valores de propiedad utilizando accesadores primitivos, evita la posibilidad de recurrencia infinita, pero Core Data no notará el cambio que realiza". –

+0

@PietroRea ¿se guardará en la base de datos? – Andy

6

Obviamente, ya hay varias buenas soluciones para esta pregunta, pero quería lanzar una nueva que funcionara mejor para un escenario particular que encontré.

(En :) Swift

override func willSave() { 
    if self.changedValues()["modificationDate"] == nil { 
     self.modificationDate = NSDate() 
    } 

    super.willSave() 
} 

La razón por la que necesitaba esto es porque tengo la peculiar requisito de la necesidad de configurar manualmente a veces el modificationDate. (La razón a veces me puse manualmente la marca de tiempo es porque trato de mantenerlo sincronizado con la hora en el servidor.)

Esta solución:

  1. Previene el bucle willSave infinito() porque una vez la marca de tiempo se establece, aparecerá en changedValues ​​()
  2. no requiere el uso de la observación
  3. permite configurar manualmente la marca de tiempo
+0

interesante. estamos tratando de evitar el uso de marcas de tiempo del cliente para cualquier cosa crítica en el servidor. el reloj de los dispositivos no es confiable, puede causar errores fuera de orden en los algoritmos de sincronización. (Es común almacenar un valor 'lastSynced' en el cliente y usarlo para pedirle al servidor solo aquellos objetos que han cambiado). – Sam

+0

@Sam, estoy usando esto para datos en los que el usuario que lo edita tiene la * autoridad * para resolver conflictos de combinación. Aunque no proporciono una IU para resolver, entonces solo uso las marcas de tiempo para resolver automáticamente los conflictos. Como el usuario tiene autoridad sobre estos datos, no me molesta que su reloj esté equivocado. Puede molestar a mi usuario, pero probablemente también se molestarán si me pierdo sus citas y cosas así :). Pero su punto es válido, y no creo que utilice este método para datos de propiedad pública. –