There are several issues with the implementation you provided in your question. Your access providers and KVOs are working against you and seem to be interfering with KVO-based master data change tracking.
Here are some of the things you can improve that can solve the problem you are seeing. This will certainly help some of the problems that you may not have encountered yet, but certainly will.
observeValueForKeyPath:ofObject:change:context: correct implementation should check the context pointer for a known value that you passed to addObserver:forKeyPath:context: and removeObserver:forKeyPath:context: This allows you to distinguish your observations from others. This leads to the next point - Proper implementation calls super. If the context value is NOT your known value, set it aside for super. Example:
Adding an observer:
[self addObserver:self forKeyPath:keyPath options:options context:(__bridge void*)self];
Removing an observer:
[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){
CoreData makes extensive use of KVO to track changes to managed object snapshots. When using KVO with Core Data, you should be aware of this and be careful not to correctly implement KVO for Core Data, which is slightly different from other objects. For example, automatic KVO notifications are disabled by default for simulated properties of NSManagedObject subclasses. This means that if a property is supported by a model attribute, it will by default not generate external KVO notifications. Properties that do not exist in the model will be.
To enable automatic KVO notifications for a simulated property, implement the class method using the following template:
+ (BOOL) automaticallyNotifiesObserversFor<PropertyName> { return YES; }
Where is the name of the property being modeled (i.e. automaticallyNotifiesObserversForSavedState ).
In your case, you have chosen to implement custom accessories for your model property. It is not clear why you decided to do this from the code you posted (you may have seen a scary warning in the willSave: documentation willSave: about recursion - your messages will / didChangeValueForKey re-enter this). It is very rarely necessary to provide your own access implementation for a subclass of a managed entity. Typically, Core Data provides an accessory for @dynamic properties at runtime. When he does this, he provides an implementation that has proper memory management and change tracking, as well as optimizations for CPU and memory.
Primitive access methods are designed to access the attributes of a managed entity. This essentially means an instance variable supported directly by the value from the data model. Access to attributes as primitive values ββis not recommended and is very rarely worth it. Always prefer that property accessors get the right behavior from Core Data and your model objects.
- Correct the implementation of KVO using the above guide.
- Do not override
init with subclasses of managed objects. init not a designated initializer. - Go from implementing your own accessor to your model property to provide Core Data with an implementation opportunity. It should be as simple as deleting the current access implementation.
- If you change NSKeyValueObservationOptions to include NSKeyValueObservingOptionInitial, you will receive a KVO notification for the initial value of the observed key path. In your case, it will also be an opportunity to set the initial state for your state machine.
Since you are implementing some kind of state machine, it is probably recommended to allow KVO to manage your dependencies between values ββ(for example, between forcedState, savedState and stateMachine). For example, set saveState as the dependent key path of the forcedState, so that when saveState changes, the system knows that the forced state must be dirty and needs to be recalculated:
+ (NSSet *)keyPathsForValuesAffectingValueForForcedState { return [NSSet setWithObject:@"savedState"];
}
Updating your final machine from managed object lifecycle methods will probably not be necessary after fixing your KVO implementation. If you decide to stick with your lifecycle methods, see If awakeFromSnapshotEvents: More suited to your needs.
Since you do not seem to be mutating your state machine too much by simply recreating it, your precedent is pretty simple. If I read your class correctly, all you have to do is set stateMachine to nil when KVO tells you that savedState has changed. If it is zero when state is available, call the create method to set the property. Most transient properties are implemented in this way.