Re: Preserving undo actions on deleted targets
Re: Preserving undo actions on deleted targets
- Subject: Re: Preserving undo actions on deleted targets
- From: Graham Cox <email@hidden>
- Date: Thu, 30 Jan 2014 10:09:10 +1100
On 30 Jan 2014, at 3:03 am, Michael Starke <email@hidden> wrote:
> NSUndoManager prepareWithInvocationTarget:
>
> the proxy that wraps the NSInvocation retains the target. Thus using a [[undoManager prepareWithInovationTarget:myObject] remove] call should ensure the object lives as long as the undo stack is valid. Works for me. If not, I might have a bug there lingering too :)
This isn't quite the case.
The target of an undo invocation is not normally retained. NSInvocation *can* be set to retain its target, but under normal circumstances when it's used by NSUndoManager, it does not. If you think about it, that's correct, because otherwise retain cycles would all too easily be set up, e.g. NSDocument retains NSUndoManager which retains the NSInvocations that wrap the undo actions. If the target of an undo action were NSDocument itself - quite reasonably, because NSDocument typically owns your data model - then you have a situation where the document will never be released because the undo manager is retaining it (many times for a large undo stack).
Instead, the target isn't retained, but the parameters to the invocation are, if they're objects. In normal circumstances, this is correct, safe and gives proper memory management, even in the case of adding or deleting objects, because for those operations, the target of the undo is NOT the objects being added or deleted, but the object that OWNS the added or deleted objects, i.e. some higher entity in your data model. For this case, the objects being added/deleted are parameters to the operation, not the target, so they're retained. Because of this, other undoable operations where the target *is* the added object work correctly, e.g. if you added an object and then changed its state, both operations can be undoable, because the add operation targeted a higher level object and retained the added object as a parameter, and the state change targeted the added object, which is being retained by an earlier invocation. It makes correct memory management sensitive to the ordering of undo tasks within the stack, but that's OK as long as you make sure every thing you do to your data model is undoable - which you probably want. If you leave out e.g. adding or deleting, but make all state changes to these objects undoable, chances are sooner or later you'll get into trouble.
If your head is spinning and your mind boggled, welcome to the club. Undo does that to you.
My simple guidelines for Undo nirvana are: 1. Undo *everything*. 2. Always make the target the object that owns the state you're undoing.
Rule 2 means, for objects being added or deleted, that the target should be the container of the added or deleted objects, not the added or deleted objects themselves.
A further thing to note is that one nice thing about NSUndoManager (there's a nice thing? who knew!) is that if you make every operation on your data model undoable, higher level code can cause several operations to be performed one after the other if necessary and Undo will group these all together automatically so Undo should "Just Workâ˘" in reversing all of these operations. There's nothing that says that all of the targets within a group must be the same, and you'll find that the order of tasks in the stack and the retains involved are exactly correct to ensure memory management is correct as well.
--Graham
_______________________________________________
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