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 (Cocoa-dev@lists.apple.com)

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:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to