Re: Core Data, transient ivars and undo
Re: Core Data, transient ivars and undo
- Subject: Re: Core Data, transient ivars and undo
- From: Andre <email@hidden>
- Date: Sun, 19 Feb 2006 20:48:00 -0800
On 平成 18/02/14, at 10:53, Christiaan Hofman wrote:
On 14 Feb 2006, at 8:00 PM, Andre wrote:
On 平成 18/02/14, at 1:03, Christiaan Hofman wrote:
On 14 Feb 2006, at 10:27 AM, email@hidden wrote:
On 平成 18/02/13, at 6:43, Christiaan Hofman wrote:
On 13 Feb 2006, at 1:09 AM, email@hidden wrote:
Christiaan Hofman wrote:
I am very much confused about undo in Core Data, in
particular when it comes to "transient" values and other
dependent state information. AFAICS the transient value or
state info is not updated in any way in undo, which can
easily lead to inconsistent states in CD apps. How should I
handle this in general?
For example a "transient" value stored in an ivar. Eg for a
non-standard attribute, which is stored in the MOC as data,
but has an ivar representation in a NSManagedObject subclass.
Usually, you interact with the transient value (ivar) rather
than the underlying CD value. However it is the data in the
MOC that is changed in undo, and only that. So you get an
inconsistent state. Writing (KVC compliant) accessors for the
underlying data does not help, as they are not called by undo.
In the docs and example projects like CoreRecipes many
accessors seem to have this flaw. Eg the "bounds" example in
"Non-Standard Attributes" section of the Core Data
Programming Guide. Here the bounds ivar and the boundsAsData
atribute should always be synchronized. However, AFAICS, if I
would undo setting (changing) the bounds, only the
boundsAsData attribute is changed, giving me an inconsistent
state. This seems to me a bug in CD, or at least in the docs
and the examples, as nothing is mentioned about it.
Perhaps [self setKey:@"boundsAsData"
triggerChangeNotificationForDependentKeys:[NSArray
arrayWithObject:@"bounds"]] ?
Since, bounds is not "known" by core data that it is related
to boundsAsData, it needs to be told they are, I beleive that
telling core data to generate a change note when boundsAsData
changes to also notify observers of the change to bounds,
would call the getter in your bounds method, and reverse the
change.... have you tried this?
No, this would not work. This way just observers are told that
bounds has changed as well, however the actual value of the
ivar bounds has not changed, which is the real problem. When
the getter is called the stale value for bounds is found and
returned. No new value is generated as it was not reset (note
that it is only regenerated when it has the proper zero value,
otherwise it would always be regenerated).
I see, how about in the boundsAsData method, override it to also
reset the cached value of bounds so that the next time bounds is
called, the cached value is non-existent, then it can regenerate
the cache and return it the next time bounds is called? Would
that work?
The question is: where should the cached (derived) data be reset?
You seem to suggest here to do it in he getter, but that is
equivalent to recalculating the derived data every time, making
the cache useless, and this can become very inefficient (note
that the getter cannot know if it was called after it was
invalidate unless it was already invalidated). Doing this in a
setter will not work, as undo does not call those. KVO dependent
keys don't call anything, they merely notify, so they cannot
invalidate the data either. I don't see any other way than KVO
observing.
I see... here was what I was thinking. In the setter for
boundsAsData, set the value of (cached) bounds (setting the
variable directly) to nil. Then, only if the value of the cached
bounds is nill, it (the bounds getter) recalculates from the
derived value and sets the cache (directly, not using KVC),
otherwise it simply calls the cached value. If there are any
observers of bounds, then the dependent key notification will let
those observers know that when boundsAsData changed, also bounds
is changed, and they will pull the data from bounds. If there are
no observers, then its OK if the bounds cache is stale, since its
nil, only the first time its called, bounds is recalculated from
the derived value, then subsequent calls are from the cache, as
long as the cache is non-nil. It should be pretty effecient....KVB/
KVO basically encourages the lazy/deferred approach to data
management AFIACS.
I think you don't understand the question. In the bounds example,
the cached bounds ivar is what I call the derived value. The
persistent managed data is the boundsAsData value. In practice most
of the app will work directly with the cached ivar.
I don 't think in such a situation, where the persistent value is
not directly accessed, there should be a need to implement a custom
accessor at all. In fact setting the cache to nil in this setter
will be useless, as the setter will never be called (by undo for
instance). Therefore the stale value of the cache will _not_ be nil
(or an appropriate zero value), which is a problem.
I would say thats a valid argument. And, I have observed this as
well, that undo does not seem to call the setter, it just "magically"
gets set. Which was why originally I though setting a dependent key
for the derived value might help, because the value would have
changed via KVO when using undo, even if the accessor is not called,
at least in my assumption, though maybe thats mistaken.
So I think either the cache should be a transient attribute in the
MOM, as in examples the docs (this is the easy case in which I am
not interested).
Or any refreshing of cache values and other dependent data should
be done through KVO observation method. I see no other way.
So something like the following:
- (void)refresh {
[self willChangeValueForKey:@"dependentValue"];
dependentValue release];
dependentValue = nil;
[self didChangeValueForKey:@"dependentValue"];
// this can do more complicated stuff, like refreshing a smart group
}
- (id)dependentValue {
if (dependentValue == nil) {
dependentValue = [[self recalculateDependentValue] retain];
}
return dependentValue;
}
- (void)setDependentValue:(id)value {
NSData *persistentData = [self
calculateDataFromDependentValue:value];
[self setValue:persistentData forKey:@"persistentData"];
// the dependent value will be automatically refreshed through KVO
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void)context {
if ([keyPath isEqual:@"persistentData"]) {
[self refresh];
}
}
Would this be the basic way to implement this kind of situation?
I think that may be a way, how does it work when you implemented it?
Since its not a technique exemplified in the documentation AFAICS,
its up to ourselves to discover if its the "right way" or not I would
think in this case. Here: http://developer.apple.com/documentation/
Cocoa/Conceptual/CoreData/index.html the only sanctioned way
mentioned is with another accessor, and the non-standard attribute to
be modeled in the model as an undefined transient attribute...
Here is something I found on something close to this subject: http://
www.awprofessional.com/articles/article.asp?p=432805&seqNum=8&rl=1
I tried to think about the third option, where I implement by hand
undo on the derived values. But this has a problem with redo. Or am
I wrong?
You could do that, as I have thought about it myself in some
situations..... coredata uses the same undo manager as everything
else, so that may be a possible way. But there may be some state
information in core data that we don't know about, and something that
coredata gets undone, then it may not be doing just what we think. I
would say, the closest we get to the undo mechanism is using key
value change notification for attributes and the change key using set
mutation notification for to-many. I haven't seen any articles about
manual undo in core data.
If you have any results, please let me know what you find, I'm
curious myself.
Andre
email@hidden
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Cocoa-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden