Re: NSUndoManager retain/release of arguments - ad infinitum
Re: NSUndoManager retain/release of arguments - ad infinitum
- Subject: Re: NSUndoManager retain/release of arguments - ad infinitum
- From: John Bartleson <email@hidden>
- Date: Mon, 10 Jan 2011 20:37:55 -0800
Thanks for your replies. I'm hoping that the conclusion will help
others who are confused by the existing
documentation of MM in NSUndoManager. So here's what I'm doing:
My reference-counted, document-based app uses a table view. The table
view supports sorting by clicking in
the header. The app supports undo. Undoing the sort is where things
got complicated. Here's the code I call when
the tableView:sortDescriptorsDidChange: dataSource method determines
that no old sortDescriptors exist for
the data array, meaning the array is initially unsorted:
// **** Part 1 ****
- (void)sortUsingNewDescriptors:(NSArray *)theNewDescriptors
tableView:(NSTableView
*)theTableView
{
NSMutableArray *theArrayToSort = [/*index into database*/
itemArray];
// Snapshot with a shallow copy so the user gets the original
unsorted array on Undo
NSMutableArray *unsortedArray = [theArrayToSort
mutableCopyWithZone:nil];
[[[self undoManager] prepareWithInvocationTarget:self]
replaceArrayWithArray:unsortedArray
andDescriptors:nil
tableView:theTableView
];
[theArrayToSort sortUsingDescriptors:theNewDescriptors];
[theTableView reloadData];
}
// **** Part 2 ****
- (void)replaceArrayWithArray:(NSMutableArray *)newArray
andDescriptors:(NSArray *)newDescriptors
tableView:(NSTableView
*)theTableView
{
// Save pointer to current array
NSMutableArray *currentArray = [/*index into database*/ itemArray];
NSArray *currentDescriptors = [theTableView sortDescriptors];
[[[self undoManager] prepareWithInvocationTarget:self]
replaceArrayWithArray:currentArray
andDescriptors:currentDescriptors
tableView:theTableView
];
// Move pointer to new array into the data
[/*index into database*/ setItemArray:newArray]; // @property
(readwrite, assign) NSMutableArray *itemArray;
signalDescriptorChange = NO; // Temporarily turn off
tableView:sortDescriptorsDidChange: dataSource
[theTableView setSortDescriptors: newDescriptors]; //- method
call so we don't get re-entered
signalDescriptorChange = YES;
[theTableView reloadData];
}
In Part 1, I make a shallow copy of the unsorted array for later
possible use by Undo, then set up the Undo
with the prepareWithInvocationTarget: call, then finally sort the
original array.
Part 2 will get called if Undo (or Redo) is selected by the user. I
save pointers to the current data and its
descriptors, set up for a Redo using prepareWithInvocationTarget:,
then replace the data array and its descriptors
with the ones in the previous prepareWithInvocationTarget: call.
The general effect of all this is that the unsorted and sorted data
arrays are swapped as the user selects Undo and Redo.
Here's the problem: in Part 1, the mutableCopyWithZone: call allocates
a new unsortedArray. In a reference-counted
environment, it (or the original theArrayToSort, depending on the
user's selection of Undo/Redo) won't be released
as currently coded and there'll be an ugly memory leak.
Since I allocated unsortedArray it's my responsibility to release it.
But if I release it after the prepareWithInvocationTarget:
call (in Part 1) it causes a crash on Redo. I also thought of doing a
retain/release when swapping the arrays in Part 2, but
that can't be a complete solution because unsortedData will still
never be released if the user never does Undo.
(As Aaron Hillegass says in his book, "Sometimes when I think about
undo, my head starts to swim a bit." By the way, kudos to Aaron for
devoting a chapter to undo in his book. I can't find any other
reference besides Apple's rather limited "Undo Architecture".)
As a possible solution, I can imagine writing a new class, let's call
it 'UndoMemory', that maintains a pool of pointers that need
to be released when the document is deallocated. In Part 1 (above), I
would call an UndoMemory method that adds
the unsortedArray pointer to the pool. In Part 2, I would call another
UndoMemory method that removes newArray
from the pool and adds the currentArray pointer. At document
deallocation time I would call a third method that releases
any pointers remaining in the pool.
UndoMemory would probably work but seems too complicated. I'd be
writing a new layer of memory management, with potential gotchas.
Note that this isn't just a sort-specific problem. Any Undo of a
memory-allocated variable (as opposed to a primitive)
in a reference-counted environment will stumble on this problem.
On Jan 9, 2011, at 10:12 PM, Graham Cox wrote:
Actually, it doesn't really matter what NSUndoManager does with
arguments, all you really need to know is that it follows the rules
for ownership of objects, so by and large does the 'right thing'. It
does lots of wrong things, IMO, but incorrect memory management
isn't among them.
_______________________________________________
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