Re: grouping undo across method calls in CoreData [with possible solutions]
Re: grouping undo across method calls in CoreData [with possible solutions]
- Subject: Re: grouping undo across method calls in CoreData [with possible solutions]
- From: Jim Thomason <email@hidden>
- Date: Tue, 6 Oct 2009 10:24:30 -0500
Figured I'd address all the comments inline in one batch, and then
point out what I came up with. An almost viable solution is up top for
reference purposes, and a seemingly better one is towards the bottom.
>Hm, do operations using primitive accessors also get registered on the undo stack? If not, you could maybe use that approach, >so the setting of the "ordered" value would not ever get registered?
This actually "worked" in that I didn't end up with the extra undo
into the interim state, but the array controller put the objects into
the wrong row. My array controller was sorted on the ordered value,
and by setting it through the primitives, the notes never fired to
re-sort it. Firing the KVO notes myself fixed it, but that also added
an empty frame onto the stack again, so back to square one.
I was able to make this work by subclassing NSArrayController and
overriding addObject: to fetch immediately afterwards.
-(void) addObject:(id) newObject {
[super addObject:newObject];
[self fetch:nil];
}
In fact, using this approach, I didn't need to detach the selector at
all and could just set the value in awakeFromInsert:, though I don't
have any idea if not detaching and setting it directly is going to
cause other bad problems. But, there's a slight visual hiccup
sometimes using this approach - When I tried integrating it into my
actual app, you could briefly see both items appear and then one
vanish. As a result, I'm not thrilled with using this approach.
Also note that it wouldn't scale well, since it'd cause a refetch of
the entire array's contents upon every insert. Fortunately, I have
fairly small object sets, so this may be viable for me.
I hope I list a better idea at the end of this email.
>I guess it is tricky dealing with begin/endUndoGrouping when using delayed invoking. Still, AFAIK it should work. Can you >elaborate on why it does not?
I don't remember exactly what was happening with it, I could make it
crash, but haven't been able to reproduce in simplified test cases so
I'm assuming the error existed elsewhere. Regardless in further
testing, if I begin/end in the method performed after the delay, it
has no effect and I still need to do it in two steps. If I begin in
awake from insert and end in the method after the delay, I get the
duplicate row on the array controller.
>>I also tried popping all references to the newly created object off
>>the stack and using prepareWithInvocationTarget: with a method that
>>just drops the object:
>Which extra undo state do you have? What is the registered undo that does nothing?
The problem was that the object's creation apparently creates two
separate sets of undo information - one for the object itself, and one
for its managed object context. I remove the undo actions for the
object, but can undo twice because one undo calls my prepared
invocation, and the second undo undoes the object add that the context
performed.
That's also why I can now redo twice and create a ghost object - my
object is recreated, but the managed object context also redoes its
object insertion, so it shows up as a default object with no post-set
values. Further, I refer to it as a ghost since it doesn't actually
exist - if you save the document and re-open it, the ghost vanishes.
If I try to use the undo manager to remove all actions on the context
(a dangerous action anyway, since I'm not sure what other existing
actions may be there), I can still undo twice. But this time, once the
doc is back in its clean newly opened state, the context still lists
the ghost as being deleted. An attempt to save here causes the app to
crash, which makes sense, since it'd be attempting to delete a
non-existent object.
I haven't a clue if it's possible to make this technique work.
>You might consider a different approach. Instead of trying to bend Core Data undo to your will, you might be able to finesse >the situation by preparing all the information for your object creation *first*, then creating the object in a single event cycle >(and hence undo action).
I considered this...but that would then require a lot of management
outside of creation to ensure that I'm keeping this cached calculated
value in sync with reality. Including making sure that the cached
value is incremented upon object insertion, and properly decremented
upon deletion (it's decremented only if you delete the highest order
object - if you delete something in the middle, we end up with a hole
in the order, but that's okay, we just keep marching ever upward). So
that would've meant finding places to add hooks for add and delete and
just generally seemed like a mess. I'm pretty sure I would've needed
an additional method call before all deletions, as per an earlier
thread of mine about coredata pre-delete hooks.
===
But, through all of that, I may actually have a viable solution - I
subclassed NSArrayController and added createOrder to my addObject:
method there:
@implementation MyArrayController
-(void) addObject:(id) newObject {
[super addObject:newObject];
[newObject createOrder];
[self rearrangeObjects];
}
Note that the rearrangeObjects call is required to ensure that its
inserted at the right spot. This is much much cheaper than refetching
everything as per my first idea. Order of operations here is
important! If you call createOrder first, and then super addObject,
you'll end up with duplicates in the array controller again (though
you wouldn't need to call rearrangeObjects).
And that's it. All seems well. I create my object, and createOrder
sets up its default values and I'm off to the races. This technique
also works just fine for setting up default relationships, which I was
previously trying to do in awakeFromInsert: and having issues
regarding duplicate entries in the array controller as well.
Of course, this assumes that all objects you create go in through an
ArrayController like this, but that worked for me.
Sometimes the simplest solutions seem easiest. I'll continue to test
and will post again if this turns out to be another dead end, but I'm
quite hopeful about it.
-Jim...
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden