NSFetchedResultsController no lee el valor derivado actualizado en la entidad CoreData después de la actualización RestKit EntityMapping

Tengo un controller de vista donde creo un NSFetchedResultsController para mostrar un set de objects CoreData en un TableView. Al ver estos objects en la vista de tabla, se actualizan llamando a un getObjects getObjects de RestKit, que procesa correctamente a través de un descriptor de respuesta y RKEntityMapping para actualizar un campo en los objects CoreData. Sin embargo, esta entidad particular también tiene un campo derivado personalizado, en realidad una máquina de estado (TransitionKit basada) que lee el valor de estado proporcionado a la entidad y reinicializa la máquina de estado con el estado proporcionado por el server. Sin embargo, sin importar dónde reinicialice la máquina de estado (awakeFromFetch, willSave, key-value observing), esta máquina de estado reinicializada no se actualiza cuando se utiliza la copy del object en NSFetchedResultsController para actualizar la celda de tabla correspondiente (cuando NSFetchResultsController es notificado del cambio en esa fila). Para ser claro: el valor que se actualiza a través de RestKit EntityMapping IS se actualiza, pero la máquina de estado (un valor derivado) no se actualiza. ¿Por qué sería esto?

¿No debería notificarse la matriz de objects de NSFetchedResultsController de manera tal que les permita calcular sus valores derivados? Cuando rastrea en el código, awakeFromFetch en el subprocess principal aún no contiene el valor actualizado, y calcular mi valor derivado en willSave o un setter no parece crear este valor derivado en la instancia del object que retiene el NSFetchedResultsController .

He adjuntado mi código de model base

#import "VCStateMachineManagedObject.h" @interface VCStateMachineManagedObject () @property (nonatomic, strong) TKStateMachine * stateMachine; @end @implementation VCStateMachineManagedObject @dynamic savedState; @synthesize stateMachine = _stateMachine; @synthesize forcedState; -(id)init { self = [super init]; if(self != nil) { } return self; } - (BOOL)canFireEvent:(id)eventOrEventName { return [_stateMachine canFireEvent:eventOrEventName]; } - (BOOL)fireEvent:(id)eventOrEventName userInfo:(NSDictionary *)userInfo error:(NSError **)error{ return [_stateMachine fireEvent:eventOrEventName userInfo:userInfo error:error]; } - (void) assignStatesAndEvents:(TKStateMachine *) stateMachine { [NSException raise:@"Invoked abstract method" format:@"Invoked abstract method"]; } - (NSString *) getInitialState { [NSException raise:@"Invoked abstract method" format:@"Invoked abstract method"]; return nil; } - (void)awakeFromInsert { if(self.savedState == nil){ self.savedState = [self getInitialState]; } [self createStateMachine]; } - (void)awakeFromFetch { if(self.savedState == nil){ self.savedState = [self getInitialState]; } [self addObserver:self forKeyPath:@"savedState" options:NSKeyValueObservingOptionNew context:nil]; [self createStateMachine]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if(![[_stateMachine.currentState name] isEqualToString:self.savedState]){ [self createStateMachine]; } } - (void) willSave { NSLog(@"%@", self.savedState); [self createStateMachine]; } // Manually set the state, for restkit object mapping - (void) setForcedState: (NSString*) state__ { self.savedState = state__; } - (void) setSavedState:(NSString *)savedState__{ [self willChangeValueForKey:@"savedState"]; [self setPrimitiveValue:savedState__ forKey:@"savedState"]; [self didChangeValueForKey:@"savedState"]; [self createStateMachine]; } - (NSString *) state { NSString * state = [_stateMachine.currentState name]; return [NSString stringWithFormat:@"%@ %@", state, self.savedState]; } #pragma mark - State Machine - (void)prepareStateMachine { for(TKEvent * event in _stateMachine.events){ [event setDidFireEventBlock:^(TKEvent *event, TKTransition *transition) { self.savedState = transition.destinationState.name; }]; } } - (void) createStateMachine { _stateMachine = [TKStateMachine new]; [self assignStatesAndEvents:_stateMachine]; [self prepareStateMachine]; _stateMachine.initialState = [_stateMachine stateNamed:self.savedState]; [_stateMachine activate]; } @end 

@quellish Esto es lo que veo cuando rastrea puntos de interrupción en mi object administrado.

  1. Llamo al descanso para download nuevos objects
  2. Restkit descarga objects en un hilo de background (no en el hilo principal). Lo veo encontrar el object coincidente (awakeFromFetch), actualizar el estado (setForcedState) y save (willSave), y en esta instancia del object veo llamar al método createStateMachine varias veces (lo que es porque lo tengo en todas estas funciones , aunque eso, por supuesto, no tiene ningún efecto en la instancia en el NSFetchedResultsController).
  3. Luego veo que un object en el hilo principal se recupera (awakeFromFetch) y pasa por el mismo process.
  4. Luego veo que un object en el hilo principal se desencadena por KVO y nuevamente reviso el método createStateMachine.

en todas las causas cuando veo variables en la máquina de estado, se han actualizado al valor correcto. pero LUEGO, cuando NSFetchedResultsController desencadena el cambio, la máquina de estado NO se actualiza aunque el valor sea el mismo.

En las próximas horas, voy a rastrear esto y brindar información aún más específica sobre el comportamiento que estoy viendo. También voy a agregar un UUID a cada instancia de la entidad para asegurarse de que el que se está actualizando es realmente el que está en el NSFetchedResultsController. Manténganse al tanto.

La implementación que proporcionó en su pregunta tiene algunos problemas. Su acceso y las implementaciones de observación de KVO están trabajando en su contra y parecen interferir con el seguimiento de cambios de Core Data, que está basado en KVO.

Estas son algunas de las cosas que puede mejorar para solucionar el problema que está viendo. Definitivamente, esto ayudará a algunos problemas que aún no se haya encontrado, pero sin duda lo hará.

observeValueForKeyPath:ofObject:change:context: Una implementación correcta debería comprobar el puntero de context contra el valor conocido que pasó a addObserver:forKeyPath:context: y removeObserver:forKeyPath:context: Esto le permite diferenciar sus observaciones de otras personas. Lo que lleva al siguiente punto: una correcta implementación llama super. Si el valor de context NO es su valor conocido, diferir a super. Ejemplo:

Agregar el observador:

 [self addObserver:self forKeyPath:keyPath options:options context:(__bridge void*)self]; 

Eliminando al observador:

 [self removeObserver:self forKeyPath:keyPath context:(__bridge void*)self]; 

observeValueForKeyPath:ofObject:change:context: implementation:

 - (void) observeValueForKeyPath: (NSString *) keyPath ofObject: (id) object change: (NSDictionary *) change context: (void *) context { if ((__bridge id)context == self){ // This is our observation, handle it here. [self setStateMachine:nil]; } else { // This is important for Core Data to work correctly. [super observeValueForKeyPath: keyPath ofObject: object change: change context: context]; } } 

CoreData usa KVO extensivamente para rastrear los cambios a las instantáneas de objects gestionados. Cuando use KVO con Core Data, debe tenerlo en count, y tenga cuidado de no implementar correctamente KVO para Core Data, que es ligeramente diferente que para otros objects. Por ejemplo, las notifications automáticas de KVO están desactivadas de manera pnetworkingeterminada para las properties modeladas de las subclasss NSManagedObject. Esto significa que si una propiedad está respaldada por un atributo modelado, no emitirá notifications KVO externas por defecto. Las properties que no existen en el model lo harán .

Para habilitar las notifications automáticas de KVO para una propiedad modelada, implemente un método de class usando el siguiente patrón:

 + (BOOL) automaticallyNotifiesObserversFor<PropertyName> { return YES; } 

¿Dónde está el nombre de la propiedad modelada (es decir, automaticallyNotifiesObserversForSavedState notifica los automaticallyNotifiesObserversForSavedState )?

En su caso, ha elegido implementar accesores personalizados para su propiedad modelada. No está claro por qué eligió hacer esto desde el código que publicó (quizás haya visto la temible advertencia en el willSave: documentation sobre la recursion; sus posts willChangeValueForKey reintroducirán esto). Es muy raro que sea necesario proporcionar su propia implementación de acceso para una subclass de object gestionado. Normalmente Core Data proporciona un acceso para properties @ dynamic en time de ejecución. Cuando lo hace, proporciona una implementación que tiene la administración correcta de la memory y el seguimiento de cambios, así como también optimizaciones para la CPU y la memory.

Los methods de acceso primitivo son para acceder a los attributes de un object gestionado. Esto esencialmente significa una variable de instancia respaldada directamente por el valor del model de datos. No se recomienda acceder a los attributes como valores primitivos, y muy raramente vale la pena. Siempre prefiera los accesores de propiedad para get el comportamiento correcto de Core Data y sus objects model.

  • Arregle su implementación de KVO usando la guía anterior.
  • No reemplace init en subclasss de objects administrados. init no es el initialzier designado.
  • Pase de implementar su propio acceso para su propiedad modelada a permitir que Core Data brinde la implementación. Esto debería ser tan simple como eliminar su implementación de acceso actual.
  • Si cambia su NSKeyValueObservationOptions para include NSKeyValueObservingOptionInitial, obtendrá una notificación KVO para el valor inicial de la ruta de la key observada. En su caso, esta sería una oportunidad para establecer también un estado inicial para su máquina de estado.
  • Dado que está implementando algún tipo de máquina de estado, es probable que sea una buena idea permitir que KVO administre sus dependencies entre valores (es decir, entre forState, savedState y stateMachine). Por ejemplo, establezca savedState como una vía de acceso dependiente de forcedState, de modo que cuando el estado de saveState cambie, el sistema sabe que forcedState debería estar "sucio" y necesita un recálculo:

     + (NSSet *)keyPathsForValuesAffectingValueForForcedState { return [NSSet setWithObject:@"savedState"]; 

    }

  • Actualizar la máquina de estado desde los methods del ciclo de vida del object gestionado probablemente no será necesario una vez que se haya resuelto la implementación de KVO. Si decide seguir los methods del ciclo de vida, vea si awakeFromSnapshotEvents: es más adecuado para sus necesidades.

Dado que no parece estar mutando su máquina de estado en absoluto, simplemente recreando, su caso de uso es bastante simple. Si estoy leyendo correctamente tu class, todo lo que necesitas hacer es establecer stateMachine a cero cuando KVO te diga que savedState ha cambiado. Si es nulo cuando se accede al state , llame a su método de creación para establecer la propiedad. La mayoría de las properties transitorias se implementan de esta manera.