在RestKit EntityMapping更新之后,NSFetchedResultsController不读取CoreData实体中更新的派生值

dee*_*ter 6 core-data objective-c ios restkit

我有一个视图控制器,我在其中创建一个NSFetchedResultsController以在TableView中显示一组CoreData对象.在tableview中查看这些对象时,可以通过调用RestKit getObjectsAtPath来更新它们,它通过响应描述符和RKEntityMapping正确处理,以更新CoreData对象上的字段.但是,此特定实体还具有自定义派生字段 - 实际上是状态机(基于TransitionKit),它读取提供给实体的状态值,并使用服务器提供的状态重新初始化状态机.但是,无论我在哪里重新初始化状态机(awakeFromFetch,willSave,键值观察),当NSFetchedResultsController中的对象副本用于更新相应的表格单元格时(NSFetchResultsController为)时,不会更新此重新初始化状态机.通知该行的变化).要清楚 - 通过RestKit EntityMapping IS更新的值已更新,但状态机(派生值)未更新.这是为什么?

不应该以允许它们计算派生值的方式通知NSFetchedResultsController的对象数组吗?当我在代码中跟踪时,主线程中的awakeFromFetch还没有包含更新的值,并且在willSave或setter中计算我的派生值似乎不会在NSFetchedResultsController持有的对象的实例中创建此派生值.

我已附上我的基本型号代码

#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
Run Code Online (Sandbox Code Playgroud)

@quellish这是我在托管对象中跟踪断点时看到的内容.

  1. 我呼吁restkit下载新对象
  2. Restkit在后台线程(不是主线程)中下载对象.我看到它找到匹配的对象(awakeFromFetch),更新状态(setForcedState)和save(willSave),并且在对象的这个实例上我看到多次调用的createStateMachine方法(这是因为我在所有这些函数中都有它,虽然这当然对NSFetchedResultsController中的实例没有影响.
  3. 然后我看到主线程上的一个对象被获取(awakeFromFetch)并经历相同的过程.
  4. 然后我看到主线程上的一个对象被KVO触发并再次通过createStateMachine方法.

在我查看状态机中的变量的所有原因中,它们已更新为正确的值.但是,当NSFetchedResultsController触发更改时,状态机即使值本身也不会更新.

在接下来的几个小时内,我将追溯到这一点,并提供有关我所看到的行为的更具体信息.我还要为实体的每个实例添加一个UUID,以确保正在更新的实例实际上是NSFetchedResultsController中的实例.敬请关注.

que*_*ish 2

您在问题中提供的实现有一些问题。您的访问器和 KVO 观察实现对您不利,并且似乎干扰了基于 KVO 的 Core Data 更改跟踪。

以下是您可以改进的一些措施,可能会解决您遇到的问题。这肯定会帮助您解决一些可能尚未遇到但肯定会遇到的问题。

observeValueForKeyPath:ofObject:change:context:addObserver:forKeyPath:context:正确的实现应该根据传递给和 的已知值检查上下文指针removeObserver:forKeyPath:context:。这使您可以将自己的观察结果与其他人的观察结果区分开来。这就引出了下一点——正确的实现调用 super。如果上下文值不是您已知的值,请遵循 super。例子:

添加观察者:

[self addObserver:self forKeyPath:keyPath options:options context:(__bridge void*)self];
Run Code Online (Sandbox Code Playgroud)

移除观察者:

[self removeObserver:self forKeyPath:keyPath context:(__bridge void*)self];
Run Code Online (Sandbox Code Playgroud)

observeValueForKeyPath:ofObject:change:context:执行:

- (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];
    }
}
Run Code Online (Sandbox Code Playgroud)

CoreData 广泛使用 KVO 来跟踪托管对象快照的更改。当将 KVO 与 Core Data 一起使用时,您应该意识到这一点,并注意不要为 Core Data 正确实现 KVO——这与其他对象略有不同。例如,对于 NSManagedObject 子类的建模属性,自动 KVO 通知默认处于关闭状态。这意味着,如果某个属性由建模属性支持,则默认情况下它不会发出外部 KVO 通知。模型中不存在的属性将会出现

要为建模属性启用自动 KVO 通知,请使用以下模式实现类方法:

+ (BOOL) automaticallyNotifiesObserversFor<PropertyName> {
    return YES;
}
Run Code Online (Sandbox Code Playgroud)

其中 是建模属性的名称(即automaticallyNotifiesObserversForSavedState)。

在您的情况下,您选择为您的建模属性实现自定义访问器。目前尚不清楚您为什么选择从您发布的代码中执行此操作(也许您在文档中看到了willSave:有关递归的可怕警告 - 您的 will/didChangeValueForKey 消息重新引入了这一点)。很少必要为托管对象子类提供您自己的访问器实现。通常,Core Data 在运行时提供 @dynamic 属性的访问器。当它这样做时,它提供了一个具有正确内存管理和更改跟踪以及 CPU 和内存优化的实现。

原始访问器方法用于访问托管对象的属性。这本质上意味着实例变量直接由数据模型中的值支持。不建议将属性作为原始值进行访问,而且很少值得这样做。始终更喜欢属性访问器从核心数据和模型对象中获取正确的行为。

  • 使用上述指南修复您的 KVO 实施。
  • 不要重写init托管对象子类。init不是指定的首字母缩写。
  • 从为建模属性实现您自己的访问器转向允许 Core Data 提供实现。这应该像删除当前的访问器实现一样简单。
  • 如果您更改 NSKeyValueObservationOptions 以包含 NSKeyValueObservingOptionInitial,您将收到有关观察到的键路径的初始值的 KVO 通知。就您而言,这也是为状态机设置初始状态的机会。
  • 由于您正在实现某种状态机,因此允许 KVO 管理值之间的依赖关系(即forcedState、savedState 和stateMachine 之间的依赖关系)可能是个好主意。例如,将savedState设置为forcedState的依赖keypath,这样当savedState改变时,系统就知道forcedState应该是“脏”的,需要重新计算:

    + (NSSet *)keyPathsForValuesAffectingValueForForcedState {
    return [NSSet setWithObject:@"savedState"];
    
    Run Code Online (Sandbox Code Playgroud)

    }

  • 一旦您的 KVO 实现得到修复,就可能不需要从托管对象生命周期方法更新您的状态机。如果您确实决定坚持使用生命周期方法,请查看 awakeFromSnapshotEvents: 是否更适合您的需求。

由于您似乎根本没有对状态机进行太多改变,只是重新创建它,因此您的用例非常简单。如果我正确地阅读了你的课程,那么当 KVO 告诉你情况已经改变时,你需要做的就是设置stateMachine为零。savedState如果访问时它为 nil state,请调用您的 create 方法来设置该属性。大多数瞬态属性都是以这种方式实现的。