NSUndoManager retain/release of arguments - ad infinitum

  • Can someone PUH-LEASE clear up the confusion re NSUndoManager
    retaining/releasing arguments to prepareWithInvocationTarget:?

    In various places in cocoa-dev and in referenced 'net articles, such
    as here <http://cocoadev.com/index.pl?NSUndoManagerTutorial>
    and here <http://lists.apple.com/archives/cocoa-dev/2001/Jul/msg00682.html> it says that arguments ARE retained.
    In Apple's own documentation ("Undo Architecture") and here <http://cocoadev.com/index.pl?NSUndoManager> it says that
    arguments ARE NOT retained.

    Which is it? I get the impression that some time in the last ten years
    an Apple developer changed this and
    then went off to a tragic death in Cupertino traffic without anyone
    officially documenting the change.

    I've wasted untold days of what could have been productive time trying
    to straighten this out. How many others have wasted their time?

    If a change was made, when was it made (in what version of Mac OS X)?
    How do we maintain both old and new code to support this properly?

    If we check the before/after retain counts of
    prepareWithInvocationTarget: arguments, it appears that the arguments
    ARE being retained.
    But wait! According to Apple's guidelines we're not supposed to use
    retainCount for this purpose.

    And we lose an innocent bystander in the confusion: the supposedly
    hard and fast rule that if you create an object with alloc, new,
    copy, or mutableCopy you are responsible for releasing it, otherwise
    you're not responsible for releasing it.

    Apparently, the rule should be supplemented with "But if you create an
    object with alloc, new, copy, or mutableCopy, then pass it
    as an argument to prepareWithInvocationTarget: you're not responsible
    for releasing it."

    Your comments, please....
  • On Sun, Jan 9, 2011 at 12:19 AM, John Bartleson <jfbartleson...> wrote:
    > Can someone PUH-LEASE clear up the confusion re NSUndoManager
    > retaining/releasing arguments to prepareWithInvocationTarget:?

    I suppose it would be more helpful to describe what you're trying to
    do and why it's not working for you.

    I've run into weirdness with retaining of arguments as well, but a
    specific scenario would be helpful here.

    --Kyle Sluder
  • On Sun, Jan 9, 2011 at 12:19 AM, John Bartleson <jfbartleson...> wrote:

    > Can someone PUH-LEASE clear up the confusion re NSUndoManager
    > retaining/releasing arguments to prepareWithInvocationTarget:?

    PUH-LEASE stop beating your head against a black box!  Replace the inexplicable NSUndoManager with Graham Cox' open-source GCUndoManager.

    GCUndoManager is equal or better than NSUndoManager in every way.  The only cost is including the source in your project, and adding a few lines of code to do the replacement.

    http://apptree.net/gcundomanager.htm
  • 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.

    You're right that monitoring retain count isn't going to enlighten you in any useful way, so forget about that.

    Kyle is asking the right question: what are you trying to do that means that this matters?

    On 09/01/2011, at 7:19 PM, John Bartleson wrote:

    > Can someone PUH-LEASE clear up the confusion re NSUndoManager retaining/releasing arguments to prepareWithInvocationTarget:?
    >
    > In various places in cocoa-dev and in referenced 'net articles, such as here <http://cocoadev.com/index.pl?NSUndoManagerTutorial>
    > and here <http://lists.apple.com/archives/cocoa-dev/2001/Jul/msg00682.html> it says that arguments ARE retained.
    > In Apple's own documentation ("Undo Architecture") and here <http://cocoadev.com/index.pl?NSUndoManager> it says that
    > arguments ARE NOT retained.
    >
    > Which is it? I get the impression that some time in the last ten years an Apple developer changed this and
    > then went off to a tragic death in Cupertino traffic without anyone officially documenting the change.
    >
    > I've wasted untold days of what could have been productive time trying to straighten this out. How many others have wasted their time?
    >
    > If a change was made, when was it made (in what version of Mac OS X)? How do we maintain both old and new code to support this properly?
    >
    > If we check the before/after retain counts of prepareWithInvocationTarget: arguments, it appears that the arguments ARE being retained.
    > But wait! According to Apple's guidelines we're not supposed to use retainCount for this purpose.
    >
    > And we lose an innocent bystander in the confusion: the supposedly hard and fast rule that if you create an object with alloc, new,
    > copy, or mutableCopy you are responsible for releasing it, otherwise you're not responsible for releasing it.
    >
    > Apparently, the rule should be supplemented with "But if you create an object with alloc, new, copy, or mutableCopy, then pass it
    > as an argument to prepareWithInvocationTarget: you're not responsible for releasing it."

    No, this is wrong.  -prepareWithInvocationTarget: only takes one argument - the target, and targets are NOT retained (targets, delegates, data sources etc are not retained, according to other well-documented rules). Maybe you're getting confused by how -prepareWithInvocationTarget: is typically used, with a follow-on message to the object it returns on which you invoke some method which really exists within the target class. The object returned by the undo manager turns that into an NSInvocation internally, which almost certainly does retain its arguments.

    There are no exceptions to the MM rules here. If NSUndoManager needs to retain or copy the object(s) passed as arguments, it will do so according to its own logic, but if you own them, you are still responsible for releasing them when you're done with them.

    --Graham
  • 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.
  • My first thought, before I go further, is that to undo a sort, you are doing things way too complicated. You only need to pass the sort descriptors to the undo manager, not a copy of the data. Presumably adding and removing items in the data itself is also undoable, so at any time the data on which undo will operate is consistent with the data you have - there is no reason to make a copy of it. That alone could significantly simplify your code, and simpler is less buggy.

    (As an aside, the way I usually handle sorting is to have a property in my data model called -sortedData, which takes the raw data and applies the current sort descriptors to it. The resulting sorted array can be cached to avoid having to perform the sort repeatedly as long as the sort descriptors don't change. When they do change, I simply invalidate the cache (i.e. release it). The actual sort only takes place when a piece of sorted data is later requested, at which point the sorted data is cached again. The table view stores the sort descriptors for you, so you don't even need to store that as a property yourself. As suggested, to undo a sort, pass the old descriptors to the undo manager and when undo is invoked, it restores the old descriptors and once again invalidates the cache. This approach is super easy and efficient - you certainly don't need to pass the previously sorted copy of the data as an undo parameter).

    On 11/01/2011, at 3:37 PM, John Bartleson wrote:

    > [/*index into database*/ setItemArray:newArray]; // @property (readwrite, assign) NSMutableArray *itemArray;

    Specifying 'assign' here is probably wrong. It is not retaining the assigned array, so you are stating you don't own it. Yet I suspect that you do want to own it. You're relying on the undo manager retaining it because you have neglected to do so. That's probably why when you do the expected and correct release, it crashes, because once the undo manager performs the undo, it will release the invocation that is retaining the array, which will dealloc it. Since you're not retaining it, it crashes because you have a stale reference.

    That is entirely your fault, not something peculiar to the undo manager.

    I recommend not using synthesised properties until you get the hang of writing your own manual getters and setters, which help you to develop the understanding of how these things should be written. Having said that, changing this from assign to retain will probably fix your issue.

    I wonder if you have repeated the same error for other object properties which are not being retained? That would explain why you are having to rely on undo manager retaining your objects for you.

    One other thing with regard to MM and Undo. Recall that targets are not retained, so the undo manager correctly does not retain the target. Thus, as with all such code (e.g. the very similar NSNotificationCenter situation) when your target is deallocated, it should purge itself from the undo manager using -removeAllActionsWithTarget: failure to do that will also crash later when someone tries to perform an undo on a no longer existing target.

    --Graham
  • On 2011 Jan 10, at 21:39, Graham Cox wrote:

    > As suggested, to undo a sort, pass the old descriptors to the undo manager and when undo is invoked, it restores the old descriptors and once again invalidates the cache.

    But that assumes that the data was sorted with some old descriptors to begin with.  It seems like this would not work if the objects had been manually arranged into some arbitrary order by the user.

    ------

    Aside: Reading this thread really makes one appreciate Core Data.

    I've written a Core Data app which can Undo Sort.  The sortable objects are the 'many' members of to-many relationships.  Each one has an 'index' parameter, which is re-set during sorting.  When you 'Undo Sort', all of the 'index' parameters get set back to their original values.  There is no code to 'Undo Sort' or 'Redo Sort'.  It really is "for free" (well, after you've tamed Core Data's Undo Groupings).

    I should not discourage John from doing it the hard way first, though.  The usual warning against Cocoa beginners diving into Core Data applies :)
  • On 11/01/2011, at 5:35 PM, Jerry Krinock wrote:

    > But that assumes that the data was sorted with some old descriptors to begin with.  It seems like this would not work if the objects had been manually arranged into some arbitrary order by the user.

    True, but then you'd make the reordering step undoable (i.e. moveObject:toIndex: or similar registers with undo).

    To make Undo really work for you, you have to make everything undoable, then all state changes in your data model are tracked and any given undo operates on a data state that is consistent with its own internal picture of the data. I suppose if all else fails you could simply make Undo take a snapshot of the data itself but that strikes me as potentially very inefficient.

    --Graham
  • I don't mean to hijack this thread, but my first thought, for what
    it's worth is that either :-

    a) sort order is a property of the window, not your model and
    shouldn't be undoable at all.

    What happens if you want another view - say a grid view, with nice big
    icons - of the same data?. Should that be sorted in the same way as
    your tableView? Should clicking the tableView header rearrange views
    in other windows? If not.. now you have an undo-stack per window and
    you must make sure that they stay in sync. ie. Adding and removing
    items happens in both views, and removing an item from one view is
    undoable in another, but the sorting and undoing sorting only applies
    to the relevant view. This will get pretty messy pretty quickly.

    Ok, so you never want to add another view, you're happy with the
    single table view. I expect an undoable-action to be a
    saveable-action. ie, normally, if i can undo it i expect it to make
    the document dirty and need saving. This might be different in your
    app but i don't normally expect to have to Save a document because i
    switched between sorting alphabetically-ascending and
    alphabetically-descending order.

    OR b)

    the sort order is inherently part of your model, your data is an
    ordered-set and the user can arbitrarily order the items as she
    pleases. In this case you probably won't be presenting your data with
    a sort descriptor, clicking on the column header will actually
    rearrange your model data.

    Undo/Redo often makes my head hurt, and not always the fault of NSUndoManager.
  • On 11/01/2011, at 10:27 PM, steven Hooley wrote:

    > I don't mean to hijack this thread, but my first thought, for what
    > it's worth is that either :-
    >
    > a) sort order is a property of the window, not your model and
    > shouldn't be undoable at all.

    I agree. In fact I don't typically make sorting undoable, though reordering can be, depending on the object being reordered. When that is done, you should unhighlight the sorting column, though I seem to recall that there's a bug in NSTableView that means you can't force it to unhighlight very easily, even if you discard the table's sort descriptors. (I might be misremembering the details of this bug, so don't take that as gospel). Undoing such a reordering *should* put back the column highlighting for the previous sort order, but in fact it's hard to make this work so I've actually never bothered to go to those lengths and so the UI is sometimes in a state inconsistent with the data. No-one has noticed and/or complained.

    > What happens if you want another view - say a grid view, with nice big
    > icons - of the same data?. Should that be sorted in the same way as
    > your tableView? Should clicking the tableView header rearrange views
    > in other windows?

    Probably would depend on the app. I have a situation where the user can switch between two views of the same data in a single window - list view and icon view. I let the list (table) view also set the sort order for the icon view, but there's also a toolbar menu that the user can use to choose sorting in either mode. In the list mode, the table UI follows the toolbar menu. Sorting isn't undoable.

    Generally treating sorting as a view feature is ideal, and so forgetting about undo is OK. However I do find it useful to be able to ask my model for sorted data, given some sort descriptors. The model caches the result to prevent sorting for every request - to do that it has to save the descriptors so it can tell when they change. Without caching, sorting on the fly can be quite a performance drag depending on where the data comes from.

    > Ok, so you never want to add another view, you're happy with the
    > single table view. I expect an undoable-action to be a
    > saveable-action. ie, normally, if i can undo it i expect it to make
    > the document dirty and need saving. This might be different in your
    > app but i don't normally expect to have to Save a document because i
    > switched between sorting alphabetically-ascending and
    > alphabetically-descending order.

    Agree, undoing sorting is unnecessary. But if you were to, storing the sort descriptors should be sufficient provided all other data reordering is also undoable.

    > the sort order is inherently part of your model, your data is an
    > ordered-set and the user can arbitrarily order the items as she
    > pleases. In this case you probably won't be presenting your data with
    > a sort descriptor, clicking on the column header will actually
    > rearrange your model data.

    Sort descriptors are still a very useful way to handle it, you'd just make the 'current sort descriptors' also part of the same model.

    --Graham
  • On 2011 Jan 11, at 03:27, steven Hooley wrote:

    > either :
    >
    > a) sort order is a property of the window, not your model and
    > shouldn't be undoable at all…
    >
    > OR b)
    >
    > the sort order is inherently part of your model

    True, and I agree with Graham that it depends on the app.  The app I described, where Core Data comes to the rescue, is a bookmarks manager which must export a sorted order. It is one of case (b).  But many apps will be case (a).
  • On Mon, 10 Jan 2011 22:35:28 -0800, Jerry Krinock said:

    > Aside: Reading this thread really makes one appreciate Core Data.
    >
    > I've written a Core Data app which can Undo Sort.  The sortable objects
    > are the 'many' members of to-many relationships.  Each one has an
    > 'index' parameter, which is re-set during sorting.  When you 'Undo
    > Sort', all of the 'index' parameters get set back to their original
    > values.  There is no code to 'Undo Sort' or 'Redo Sort'.  It really is
    > "for free" (well, after you've tamed Core Data's Undo Groupings).
    >
    > I should not discourage John from doing it the hard way first, though.
    > The usual warning against Cocoa beginners diving into Core Data applies :)

    I agree that Core Data is helpful here.  But it's not a panacea.  For
    one, there doesn't seem to be a nice way to get the Undo menu item to
    say descriptive things like "Undo Typing", "Redo Size Change", etc.
    When doing things 'the hard way' that part's actually easier.

    --
    ____________________________________________________________
    Sean McBride, B. Eng                <sean...>
    Rogue Research                        www.rogue-research.com
    Mac Software Developer              Montréal, Québec, Canada
  • On 2011 Jan 11, at 08:35, Sean McBride wrote:

    > For one, there doesn't seem to be a nice way to get the Undo menu item to
    > say descriptive things like "Undo Typing", "Redo Size Change", etc.
    > When doing things 'the hard way' that part's actually easier.

    Oh, yes.  In my original draft of that post I had mentioned "Undo Action Names" as the second beast which one needs to tame before enjoying the marvels of Core Data's "undo for free".  I omitted it for brevity.  But, yes, it's right there after the Undo Groupings.
  • And I'd love to know a trick to bring the correct tab to the front when undoing/redoing changes in a tabbed interface.

    On 2011-01-11, at 2:00 PM, Jerry Krinock wrote:

    >
    > On 2011 Jan 11, at 08:35, Sean McBride wrote:
    >
    >> For one, there doesn't seem to be a nice way to get the Undo menu item to
    >> say descriptive things like "Undo Typing", "Redo Size Change", etc.
    >> When doing things 'the hard way' that part's actually easier.
    >
    > Oh, yes.  In my original draft of that post I had mentioned "Undo Action Names" as the second beast which one needs to tame before enjoying the marvels of Core Data's "undo for free".  I omitted it for brevity.  But, yes, it's right there after the Undo Groupings.
  • On 2011 Jan 11, at 17:54, Dave Fernandes wrote:

    > And I'd love to know a trick to bring the correct tab to the front when undoing/redoing changes in a tabbed interface.

    Well, the way I've seen it done in Apple Sample Code is to avoid a tabbed interface, do editing of objects in sheets, and the sheets have their own managed object contexts and own undo managers.  But I don't like that because the undo chain often appears to be broken from the user's viewpoint, objects must be copied between mocs when editing ends, and as far as multiple undo managers, well, I say "Thank you", but one undo manager is more than enough for me to handle on a good day.

    So, I've done the switching of tabs as you suggest, and am fairly pleased with the results, except that I'm sure I spent much more time on Undo than is worth it to my users.

    First of all, you make the simplifying assumption that the "correct" tab view item is the one that is being displayed at the time that the Undo Group was ended.  I find this to be correct about 90% of of the time.  Then, you register an undo action to select this tab view item in your current undo group.

    It sounds simple, but when you try it, all kinds of wacky things happen.  You also need to consider Redo.  The plot thickens.

    I do it via a notification with two observers:

    [[NSNotificationCenter defaultCenter] addObserver:self
                      selector:@selector(registerTabViewForUndo:)
                          name:SSYUndoManagerWillEndUndoGroupNotification
                        object:[self undoManager]] ;
    [[NSNotificationCenter defaultCenter] addObserver:self
                      selector:@selector(registerTabViewForUndo:)
                          name:NSUndoManagerCheckpointNotification
                        object:[self undoManager]] ;

    SSYUndoManagerWillEndUndoGroupNotification is, as the name implies, a notification that I post when I end an undo grouping, usually a super-group that includes a half dozen or so Core Data undo groupings.  (But that's another topic.)

    Now here's the method that it triggers.  The comments are the most interesting part.

    - (void)registerTabViewForUndo:(NSNotification*)note {
        // As always, I use [[self managedObjectContext] hasChanges]
        // instead of [self isDocumentEdited] because the former
        // often returns YES when the latter is NO, and this
        // seems to be the true answer.
        if (![[self managedObjectContext] hasChanges]) {
            return ;
        }

        // Some tab view items do not have any controls which
        // affect the data model.
        if (![[self bkmslfWinCon] activeTabViewIsUndoable]) {
            return ;
        }

        // Determine whether or not to register a change to the current tab view in the current
        // undo group.  The code with follows looks weird and unsymmetrical, but it implements
        // (efficiently) what has been determined by careful experimentation to be the
        // following Magic Algorithm:
        // doRegister if any one of:
        //  1.  SSYUndoManagerWillEndUndoGroupNotification && !isUndoing
        //      (Register during normal "do" changes by the user, to be used during later Undo.)
        //  2.  NSUndoManagerCheckpointNotification && isUndoing
        //      (Register during undo operations, to be used during later Redo.)
        //  3.  NSUndoManagerCheckpointNotification && isRedoing
        //      (Register during redo operations, to be used during later Undo.)
        // I tried combining cases 1 and 2 above, by observing NSUndoManagerCheckpointNotification
        // without regard to isUndoing, but this caused a proliferation of undo groups, needing to
        // click Undo several times before the desired action was undone.
        // I also tried to use NSUndoManagerWillCloseUndoGroupNotification instead of
        // SSYUndoManagerWillEndUndoGroupNotification, but this caused a "bad group state -
        // attempt to close a nested group with no group open" to be logged from GCUndoManager
        // immediately upon doing an action.
        NSString* name = [note name] ;
        BOOL doRegister = NO ;
        if ([name isEqualToString:NSUndoManagerCheckpointNotification]) {
            doRegister = [[self undoManager] isUndoing] || [[self undoManager] isRedoing] ;
        }
        else if ([name isEqualToString:SSYUndoManagerWillEndUndoGroupNotification]) {
            doRegister = ![[self undoManager] isUndoing] ;
        }

        if (doRegister) {
            // Register revealing the tab view item in the current undo group
            NSString* currentTabViewIdentifier = [[self bkmslfWinCon] activeTabViewItemIdentifier] ;
            [[self undoManager] registerUndoWithTarget:self
                                              selector:@selector(revealTabViewIdentifier:)
                                                object:currentTabViewIdentifier] ;
        }
    }

    Of course, bkmslfWinCon is the document's window controller, and the -activeTabViewItemIdentifier and -revealTabViewIdentifier: methods are as their names imply.  I use these wrappers since I actually have multiple levels of tabs.
  • Thanks, Jerry. I will have to try this out (if I can get up the nerve to fiddle with my currently working undo groups.)

    Dave

    On 2011-01-12, at 12:47 AM, Jerry Krinock wrote:

    >
    > On 2011 Jan 11, at 17:54, Dave Fernandes wrote:
    >
    >> And I'd love to know a trick to bring the correct tab to the front when undoing/redoing changes in a tabbed interface.
    >
    > Well, the way I've seen it done in Apple Sample Code is to avoid a tabbed interface, do editing of objects in sheets, and the sheets have their own managed object contexts and own undo managers.  But I don't like that because the undo chain often appears to be broken from the user's viewpoint, objects must be copied between mocs when editing ends, and as far as multiple undo managers, well, I say "Thank you", but one undo manager is more than enough for me to handle on a good day.
    >
    > So, I've done the switching of tabs as you suggest, and am fairly pleased with the results, except that I'm sure I spent much more time on Undo than is worth it to my users.
    >
    > First of all, you make the simplifying assumption that the "correct" tab view item is the one that is being displayed at the time that the Undo Group was ended.  I find this to be correct about 90% of of the time.  Then, you register an undo action to select this tab view item in your current undo group.
    >
    > It sounds simple, but when you try it, all kinds of wacky things happen.  You also need to consider Redo.  The plot thickens.
    >
    > I do it via a notification with two observers:
    >
    > [[NSNotificationCenter defaultCenter] addObserver:self
    > selector:@selector(registerTabViewForUndo:)
    > name:SSYUndoManagerWillEndUndoGroupNotification
    > object:[self undoManager]] ;
    > [[NSNotificationCenter defaultCenter] addObserver:self
    > selector:@selector(registerTabViewForUndo:)
    > name:NSUndoManagerCheckpointNotification
    > object:[self undoManager]] ;
    >
    > SSYUndoManagerWillEndUndoGroupNotification is, as the name implies, a notification that I post when I end an undo grouping, usually a super-group that includes a half dozen or so Core Data undo groupings.  (But that's another topic.)
    >
    > Now here's the method that it triggers.  The comments are the most interesting part.
    >
    > - (void)registerTabViewForUndo:(NSNotification*)note {
    > // As always, I use [[self managedObjectContext] hasChanges]
    > // instead of [self isDocumentEdited] because the former
    > // often returns YES when the latter is NO, and this
    > // seems to be the true answer.
    > if (![[self managedObjectContext] hasChanges]) {
    > return ;
    > }
    >
    > // Some tab view items do not have any controls which
    > // affect the data model.
    > if (![[self bkmslfWinCon] activeTabViewIsUndoable]) {
    > return ;
    > }
    >
    > // Determine whether or not to register a change to the current tab view in the current
    > // undo group.  The code with follows looks weird and unsymmetrical, but it implements
    > // (efficiently) what has been determined by careful experimentation to be the
    > // following Magic Algorithm:
    > // doRegister if any one of:
    > //  1.  SSYUndoManagerWillEndUndoGroupNotification && !isUndoing
    > //      (Register during normal "do" changes by the user, to be used during later Undo.)
    > //  2.  NSUndoManagerCheckpointNotification && isUndoing
    > //      (Register during undo operations, to be used during later Redo.)
    > //  3.  NSUndoManagerCheckpointNotification && isRedoing
    > //      (Register during redo operations, to be used during later Undo.)
    > // I tried combining cases 1 and 2 above, by observing NSUndoManagerCheckpointNotification
    > // without regard to isUndoing, but this caused a proliferation of undo groups, needing to
    > // click Undo several times before the desired action was undone.
    > // I also tried to use NSUndoManagerWillCloseUndoGroupNotification instead of
    > // SSYUndoManagerWillEndUndoGroupNotification, but this caused a "bad group state -
    > // attempt to close a nested group with no group open" to be logged from GCUndoManager
    > // immediately upon doing an action.
    > NSString* name = [note name] ;
    > BOOL doRegister = NO ;
    > if ([name isEqualToString:NSUndoManagerCheckpointNotification]) {
    > doRegister = [[self undoManager] isUndoing] || [[self undoManager] isRedoing] ;
    > }
    > else if ([name isEqualToString:SSYUndoManagerWillEndUndoGroupNotification]) {
    > doRegister = ![[self undoManager] isUndoing] ;
    > }
    >
    > if (doRegister) {
    > // Register revealing the tab view item in the current undo group
    > NSString* currentTabViewIdentifier = [[self bkmslfWinCon] activeTabViewItemIdentifier] ;
    > [[self undoManager] registerUndoWithTarget:self
    > selector:@selector(revealTabViewIdentifier:)
    > object:currentTabViewIdentifier] ;
    > }
    > }
    >
    > Of course, bkmslfWinCon is the document's window controller, and the -activeTabViewItemIdentifier and -revealTabViewIdentifier: methods are as their names imply.  I use these wrappers since I actually have multiple levels of tabs.
  • Thank you all for your comments. Because I think this is an important
    topic (and because I'm stubborn)
    I'm going to continue to beat it to death.

    Because there were a number of issues brought up in the comments, let
    me summarize them, with my
    responses in line:

    ** Comment 1 (Graham Cox, January 10, 2011 9:39:06 PM PST) **
        "My first thought, before I go further, is that to undo a sort,
    you are doing things way too
        complicated. You only need to pass the sort descriptors to the
    undo manager, not a copy of the data."
    ** Response to Comment 1 **
        It appears that I didn't properly explain that the code I
    included in my second post is only
        executed if the dataSource method determines that no old
    sortDescriptors exist for the
        data array, meaning the array is initially unsorted. If both old
    and new sort descriptors
        exist for the data array, which means that the array has been
    previously sorted, my main
        tableView:sortDescriptorsDidChange: method sets up an undo with
    swapped old and new descriptors
        and then sorts using the new descriptors. Simple, like you said.
    I didn't include this main method
        in my post. I call the code in Part 1 and Part 2 of my post only
    if, as Jerry Krinock pointed out
        (January 10, 2011 10:35:28 PM PST), "...the objects had been
    manually arranged into some arbitrary
        order by the user" and there are no old sort descriptors to use
    for undo. In this case, it seemed
        to me that the most efficient way to recover the original
    arbitrary data order was to make a
        shallow copy of the entire array.
    ** Comment 2 (Steven Hooley, January 11, 2011 3:27:47 AM PST) **
        "either : a) sort order is a property of the window, not your
    model and shouldn't be undoable at
        all.... OR b) the sort order is inherently part of your model,
    your data is an ordered-set and the
        user can arbitrarily order the items as she pleases."
    ** Response to Comment 2 **
        Good point, and one that I hadn't thought of. I had
    simplistically designed my app to use
        sort order as b) part of the model, but a) suggests interesting
    design possibilities.
    ** Comment 3 (Graham Cox, January 10, 2011 9:39:06 PM PST) **
        "Specifying 'assign' here is probably wrong. It is not retaining
    the assigned array, so you are
        stating you don't own it. Yet I suspect that you do want to own
    it. You're relying on the undo
        manager retaining it because you have neglected to do so."
    ** Response to Comment 3 **
        You're right - it's sloppy coding on my part. In my post I showed
    the @property in a comment in case
        someone would see if this was wrong. I changed the @property from
    'assign' to 'retain', as you
        suggested, and there's no crash on Redo. However, there's still a
    problem in that I don't know
        if the array I allocated (in Part 1) with mutableCopyWithZone: is
    always being released. I'll
        explain again:

    The real point of my original plaintive cry, and I'm sorry if it's
    become obfuscated, is that I
    had to allocate memory as part of setting up for undo, but I couldn't
    find any *simple* way to
    keep track of whether that memory is still in use or not, thus
    threatening a memory leak. This
    is not a sorting problem - it appears that it will occur generically
    whenever we write reference-
    counted code that allocates memory and then passes that memory,
    usually via -prepareWithInvocationTarget:,
    to an undo method.

    Graham Cox made an interesting comment earlier:
        "...once the undo manager performs the undo, it will release the
    invocation that is retaining
        the array, which will dealloc it."

    This gave me a glimmer of hope to explain what's going on. If in my
    method I allocate an
    object (see the Part 1 code in my 2nd post), save a pointer to the
    object in the local variable
    'unsortedArray', pass the pointer to -prepareWithInvocationTarget:,
    then exit the method, my pointer
    is gone. If I haven't kept a copy of the pointer (say in an NSDocument
    instance variable)
    -prepareWithInvocationTarget: (or the instance of NSUndoManager or
    whatever) is now sole
    owner of the pointer.

    Because it owns the only pointer, -prepareWithInvocationTarget: (or
    whatever) now owns the object.

    Recall that, after I fixed my @property, my code appears to work
    properly. Ignoring the fact that
    I don't know any way to verify that the object is actually being
    deallocated at NSDocument dealloc time,
    I conclude that NSUndoManager and -prepareWithInvocationTarget: must
    be designed to retain and
    release objects passed to them as needed to provide undo
    functionality. As Graham says: "it
    follows the rules for ownership of objects, so by and large does the
    'right thing'."

    There's appears to be only one problem remaining: this behavior is not
    documented anywhere I can
    find. In fact, Apple's 'Undo Architecture' implies just the opposite:
    "An NSUndoManager object does
    not retain the targets of undo operations."
  • On 13/01/2011, at 7:52 PM, John Bartleson wrote:

    > The real point of my original plaintive cry, and I'm sorry if it's become obfuscated, is that I
    > had to allocate memory as part of setting up for undo, but I couldn't find any *simple* way to
    > keep track of whether that memory is still in use or not, thus threatening a memory leak.

    You are not required to keep track of it. You allocate it, pass it on, then release it. It's no longer of any interest to you, you're done with it. Standard MM rules.

    > This
    > is not a sorting problem - it appears that it will occur generically whenever we write reference-
    > counted code that allocates memory and then passes that memory, usually via -prepareWithInvocationTarget:,
    > to an undo method.

    Let's make something clear.

    -prepareWithInvocationTarget: takes just one argument - the target to call when undo or redo is invoked. This method then returns an object - in fact a NSProxy in the current implementation, but that's not especially important to this discussion.

    While you typically write something like this:

    [[undoManager prepareWithInvocationTarget:self] performSortWithDescriptors:descriptors];

    what you're really doing is this:

    id proxy = [undoManager prepareWithInvocationTarget:self];
    [proxy performSortWithDescriptors:descriptors];

    So at no point are you passing objects such as arrays or sort descriptors to -prepareWithInvocationTarget:, you are passing the TARGET. By and large this will be 'self', the object that is setting up the undo. It is not retained.

    On the returned proxy, you call a method, which is a method you know to exist in the target (self). The proxy is set up so that, because it knows about the target already, it can 'fake' the existence of that method within itself. When you call that method, it freezes it and all its arguments into an NSInvocation which is stored by the NSUndoManager. The arguments are retained.

    > This gave me a glimmer of hope to explain what's going on. If in my method I allocate an
    > object (see the Part 1 code in my 2nd post), save a pointer to the object in the local variable
    > 'unsortedArray', pass the pointer to -prepareWithInvocationTarget:, then exit the method, my pointer
    > is gone. If I haven't kept a copy of the pointer (say in an NSDocument instance variable)
    > -prepareWithInvocationTarget: (or the instance of NSUndoManager or whatever) is now sole
    > owner of the pointer.

    No, you don't pass the pointer to -prepareWithInvocationTarget:, see above. You pass the pointer as an argument to a method of the target which is actually invoked on the NSProxy.

    This isn't as complicated as it sounds. But the distinction is important, because the proxy converts the method call to an invocation, and the invocation retains the arguments. So yes, that means that the undo manager does take ownership of the arguments, because it owns the invocations.

    By the way, while none of this is especially transparent in NSUndoManager, you can follow the code in my GCUndoManager, which uses different structures but works similarly.

    > Recall that, after I fixed my @property, my code appears to work properly. Ignoring the fact that
    > I don't know any way to verify that the object is actually being deallocated at NSDocument dealloc time,
    > I conclude that NSUndoManager and -prepareWithInvocationTarget: must be designed to retain and
    > release objects passed to them as needed to provide undo functionality. As Graham says: "it
    > follows the rules for ownership of objects, so by and large does the 'right thing'."

    You are not required to verify it. (Though you may do so using the memory tracing tools). You only need to follow the rules, and understand that the undo manager also follows the rules. When everyone follows the rules, the need to verify and check ownership disappears - you can take it as read that correct memory management is performed. In other words you only need to concentrate on ensuring YOUR code follows the rules - the frameworks can be assumed to.

    > There's appears to be only one problem remaining: this behavior is not documented anywhere I can
    > find. In fact, Apple's 'Undo Architecture' implies just the opposite: "An NSUndoManager object does
    > not retain the targets of undo operations."

    That's because you are confusing the target of the undo operation (not retained) with the arguments to the undo invocation (retained).

    The documentation is not required to state that any particular class follows the MM rules - all classes can be assumed to. Only exceptions to the standard rules need to be documented.

    --Graham
  • Graham: thanks for your comment and thanks for being patient with a
    relative n00b. Hopefully we can
    nail this topic down in a post or two.

    > You are not required to verify it. (Though you may do so using the
    > memory tracing tools). You only need to follow the rules, and
    > understand that the undo manager also follows the rules. When
    > everyone follows the rules, the need to verify and check ownership
    > disappears - you can take it as read that correct memory management
    > is performed. In other words you only need to concentrate on
    > ensuring YOUR code follows the rules - the frameworks can be assumed
    > to.

    I'm sure you'd agree that when we're learning a new environment,
    references and documentation are only
    part of the process. If we can't figure out the docs, and lacking an
    expert in the next cubicle to ask,
    then writing a test app will usually quickly answer our question. This
    is where a legitimate need to
    verify and check object ownership comes from.

    You mentioned memory tracing tools. I'm familiar with Instruments, but
    I wasn't aware you could use it
    to trace individual variables. Did you have a particular tool in mind?

    > So yes, that means that the undo manager does take ownership of the
    > arguments, because it owns the invocations.

    I'm glad to see this, because it's really the answer to my original
    question. Poking around a little, one finds
    NSInvocation's retainArguments method, which is probably what
    NSUndoManager uses.

    You've stated that I "only need to follow the rules." This left me to
    wonder which memory management rules
    I had missed that would predict the above result. My day-to-day MM
    rules include the ususal: "If you create it
    (alloc, new, copy, etc.) you own it," "You can take ownership with
    retain," "When you're finished with something
    you own, you must release it," etc.

    In addition we also learn special MM rules for useful framework
    classes, e.g. a collection will retain objects
    passed to it. I don't, however, know any special rules pertaining to
    NSUndoManager and NSInvocation. So I re-read
    Apple's "Memory Management Programming Guide." I honestly can't find
    anything in there that would be an obvious
    rule pertaining to the current topic. Which rules are relevant here?
  • On 14/01/2011, at 5:58 PM, John Bartleson wrote:

    > I'm glad to see this, because it's really the answer to my original question. Poking around a little, one finds
    > NSInvocation's retainArguments method, which is probably what NSUndoManager uses.
    >
    > You've stated that I "only need to follow the rules." This left me to wonder which memory management rules
    > I had missed that would predict the above result. My day-to-day MM rules include the ususal: "If you create it
    > (alloc, new, copy, etc.) you own it," "You can take ownership with retain," "When you're finished with something
    > you own, you must release it," etc.
    >
    > In addition we also learn special MM rules for useful framework classes, e.g. a collection will retain objects
    > passed to it. I don't, however, know any special rules pertaining to NSUndoManager and NSInvocation. So I re-read
    > Apple's "Memory Management Programming Guide." I honestly can't find anything in there that would be an obvious
    > rule pertaining to the current topic. Which rules are relevant here?
    >

    The standard rules.

    There are no special rules pertaining to NSUndoManager and NSInvocation (though with invocations, there are certain cases where not retaining the arguments might be apropos, which is why there is a -retainArguments method on NSInvocation - NSInvocations are used in places other than undo).

    I wouldn't say that  "e.g. a collection will retain objects passed to it" is a special rule. It necessarily follows that since collections own their contents, they will retain them. Most objects that store objects either singly or multiply use collection classes internally to do so, so in general whenever any object is passed to another, and is expected to live longer than a moment more, it will be retained.

    However, I think you're focusing too much on what (framework) objects do with objects you pass them from 'outside' - actually, it doesn't matter. Those objects will do as they see fit with respect to object ownership, according to the rules. You need only focus on what your code does, to see that it follows the rules.

    e.g., take this hypothetical set up for undo, based on your code:

    NSArray* descriptorsCopy = [[myTableView descriptors] copy];    // you now own 'descriptorsCopy'

    [[myUndoManager prepareWithInvocationTarget:self] sortWithDescriptors:descriptorsCopy];    // you still own 'descriptorsCopy'. Does the undo manager? Who knows nor cares?

    // do you have any further use for the descriptorsCopy? No, so you're done with it. It's now your responsibility to release it as you no longer own it

    [descriptorsCopy release];

    In the middle step, why should YOU worry about whether the undo manager has retained 'descriptorsCopy'? It's not your responsibility how that object works. It might have retained the object, copied it, turned it into something completely different, or buried it in soft peat for three months. Not your concern. All you know is, following that line, you have no further interest in the object, so you release it.

    It's when you try to second-guess the behaviour of other objects where you will run into trouble. In other words, if you attempt to compensate for a release or retain elsewhere in your code (or even worse, in code you're using that isn't yours, like a framework class). That's not following the rules. In such a case, the code analyser will flag a potential memory management error. That's a great tool for learning how to write correct code in this respect.

    > You mentioned memory tracing tools. I'm familiar with Instruments, but I wasn't aware you could use it
    > to trace individual variables. Did you have a particular tool in mind?

    I believe the 'allocation' tool can do this, though I rarely use it so my memory may be faulty. Or it might be the 'leaks' tool.

    --Graham
  • > I'm sure you'd agree that when we're learning a new environment, references and documentation are only
    > part of the process. If we can't figure out the docs, and lacking an expert in the next cubicle to ask,
    > then writing a test app will usually quickly answer our question. This is where a legitimate need to
    > verify and check object ownership comes from.

    If you differentiate between these, check out the sample code Apple has made available as well as the old WWDC videos, which may provide you with a feel like "an expert in the next cubicle."

    - Gary L. Wade (Sent from my iPhone)
  • On Jan 11, 2011, at 7:35 AM, Jerry Krinock wrote:
    > On 2011 Jan 10, at 21:39, Graham Cox wrote:
    >
    >> As suggested, to undo a sort, pass the old descriptors to the undo manager and when undo is invoked, it restores the old descriptors and once again invalidates the cache.
    >
    > But that assumes that the data was sorted with some old descriptors to begin with.  It seems like this would not work if the objects had been manually arranged into some arbitrary order by the user.

    Guys, why is sorting like this even an undo-able action in your apps ... ? That's pointless. Look at iTunes to see how you'd really do it: You'd have one column for the user-defined sort order (which conceptually contains an index by which you can sort that the user can change by dragging). So, to undo a sort, the user simply clicks another header.

    The way Jerry seems to have it right now, if I simply click a list header, my carefully hand-crafted item order is irrevocably destroyed. I can at best undo it. Sort order/list headers are a view-specific attribute. They shouldn't affect the model.

    Cheers,
    -- Uli Kusterer
    "The Witnesses of TeachText are everywhere..."
  • On 15/01/2011, at 4:50 AM, Uli Kusterer wrote:

    > Guys, why is sorting like this even an undo-able action in your apps ... ? That's pointless.

    Not always pointless. Probably mostly. Depends on the app.

    Personally I have never bothered to make sorting undoable - I was thinking hypothetically. However I have implemented the caching of sorting in the manner described (though more as a controller-level implementation than a model one) because I've found that sorting thousands of items when supplying them to e.g. a table view or icon view to be a major performance drag.

    --Graham
  • Sorry, I was busy and couldn't reply. Now it's time to finish this
    thread.

    Graham Cox said:
    > In the middle step, why should YOU worry about whether the undo
    > manager has retained 'descriptorsCopy'? It's not your responsibility
    > how that object works. It might have retained the object, copied it,
    > turned it into something completely different, or buried it in soft
    > peat for three months. Not your concern. All you know is, following
    > that line, you have no further interest in the object, so you
    > release it.

    Thanks for this statement of a generally-applicable principle. If
    we're talking about passing
    an object to, say, a collection, then it's easy to see when the
    collection has taken ownership
    (put a leash on the dog) and we can release our interest. However,
    when learning NSUndoManager
    the n00b probably won't see that NSUndoManager takes ownership of the
    method invocation
    following prepareWithInvocationTarget:self. Indeed, it looks to n00b
    as if he himself is fully
    responsible for the undo method and its arguments. After all, he sees
    it as his code rather
    than NSUndoManager's.

    A lot of the confusion comes about because of poor documentation.
    Apple's "Introduction to Undo
    Architecture" doesn't clearly explain that the sequence beginning with
    prepareWithInvocationTarget:
    results in NSUndoManager effectively taking ownership of n00b's method
    and its arguments. (Please
    forgive my inexactness in terminology here, Graham, NSUndoManager
    messes with my head!)

    That's a minor problem, though, relative to the next one: the
    documentation says "An NSUndoManager object
    does not retain the targets of undo operations." While technically
    true (the target is the object
    containing the method calling prepareWithInvocationTarget:, not the
    method to be invoked), n00b will
    invariably read this sentence to mean that NSUndoManager does not
    retain (take ownership of) the arguments
    to the method to be invoked.

    N00b's first impression will be incorrect. As Graham says "The object
    returned by the undo manager turns
    that into an NSInvocation internally, which almost certainly does
    retain its arguments."

    Looks to me like the person writing the documentation was too close to
    the implementation, and wasn't
    able to put him/herself in n00b's shoes. This is a common problem in
    technical docs.

    I will use the "Did this document help you?" response to the
    NSUndoManager web docs and see if we can
    get it updated to avoid the problems described above.

    Let me make one final point: this topic would have never come up if I
    had had a tool that would let me track the
    lifetime of an object. Very hypothetically, suppose I could write my
    code, execute it, and then, in the debugger or
    whatever, could issue a command like "Track someObjectName". During
    execution, I'd see the following:

    (timestamp) (code location) someObjectName: allocated
    (timestamp) (code location) someObjectName: init
    (timestamp) (code location) someObjectName: retained
    (timestamp) (code location) someObjectName: released
    (timestamp) (code location) someObjectName: released
    (timestamp) (code location) someObjectName: dealloc

    II realize that a tool like this appears to be against your stated
    philosophy, Graham, but it sure would
    have saved me and the Cocoa list a lot of time and frustration.
  • On 06/02/2011, at 11:37 AM, John Bartleson wrote:

    > A lot of the confusion comes about because of poor documentation. Apple's "Introduction to Undo
    > Architecture" doesn't clearly explain that the sequence beginning with prepareWithInvocationTarget:
    > results in NSUndoManager effectively taking ownership of n00b's method and its arguments. (Please
    > forgive my inexactness in terminology here, Graham, NSUndoManager messes with my head!)

    That's a big part of the problem here - you are assuming NSUndoManager is somehow infinitely more complicated than every other class. It's not. It follows the standard rules at all times. If it helps, you can think of it much like a collection - all it does is to accumulate tasks on a pair of stacks. As with any collection class, it owns its contents.

    You're also confusing yourself with respect to an object "taking ownership of [my] method and its arguments". A method isn't an object, so there is no ownership to be concerned about (except the implicit 'ownership' in a more abstract sense that a class has over its own methods, but that isn't a memory management concern for the programmer.).

    An NSInvocation turns a method into an object, but it isn't the original method - it's just a reference to it. This requires no new thinking. When an NSInvocation object is made, it is owned by whoever alloc'ed it. In this case that's NSUndoManager. Not you. End of story. Just because it embodies a reference to something you own (but you don't - methods are not owned), doesn't make it your responsibility any more than any other similar case. Invocations are not special cases, so stop trying to make that assumption fit.

    > That's a minor problem, though, relative to the next one: the documentation says "An NSUndoManager object
    > does not retain the targets of undo operations." While technically true (the target is the object
    > containing the method calling prepareWithInvocationTarget:, not the method to be invoked), n00b will
    > invariably read this sentence to mean that NSUndoManager does not retain (take ownership of) the arguments
    > to the method to be invoked.

    A noob might if they were not reading carefully. The documentation does not state that, and does not imply that. If a reader takes away that impression, they have not read it carefully. I don't think the fault can be laid at the blame of the documentation writer there. The onus is on the reader to read and understand what is actually written and stated, not what they think it does.

    > Let me make one final point: this topic would have never come up if I had had a tool that would let me track the
    > lifetime of an object. Very hypothetically, suppose I could write my code, execute it, and then, in the debugger or
    > whatever, could issue a command like "Track someObjectName". During execution, I'd see the following:

    I believe the 'Leaks' tool will give you exactly that. Or if not leaks then one of the others, but my unfamiliarity with it makes it difficult to remember which one it is. I used it once I think.
  • On 2011 Feb 05, at 16:37, John Bartleson wrote:

    > A lot of the confusion comes about because of poor documentation.

    That's one of the main reasons for using Graham's open-source GCUndoManager.

    > whatever, could issue a command like "Track someObjectName". During execution, I'd see the following:
    >
    > (timestamp) (code location) someObjectName: allocated
    > (timestamp) (code location) someObjectName: init
    > (timestamp) (code location) someObjectName: retained
    > (timestamp) (code location) someObjectName: released
    > (timestamp) (code location) someObjectName: released
    > (timestamp) (code location) someObjectName: dealloc

    Maybe there is a tool, but the brute-force approach is not difficult.  You can subclass the object, then override -retain, -release, -dealloc, and, if you want, -autorelease.  Each method should log and invoke super.  If the object is of a Cocoa class, you can do a Method Replacement on these methods instead, to get the same effect.

    However only do this as a last resort, because you usually get a fire-hose of log statements due to all of the stuff going on under the hood, which is time-consuming to sort through.  (Hint: Anything invoked internally by Cocoa is OK.)  But I have used it two or three times last year to debug nightmare leaks and retain cycles.  It's a good learning exercise in Cocoa memory management.
  • On Feb 5, 2011, at 6:37 PM, John Bartleson wrote:

    > Let me make one final point: this topic would have never come up if I had had a tool that would let me track the
    > lifetime of an object. Very hypothetically, suppose I could write my code, execute it, and then, in the debugger or
    > whatever, could issue a command like "Track someObjectName". During execution, I'd see the following:
    >
    > (timestamp) (code location) someObjectName: allocated
    > (timestamp) (code location) someObjectName: init
    > (timestamp) (code location) someObjectName: retained
    > (timestamp) (code location) someObjectName: released
    > (timestamp) (code location) someObjectName: released
    > (timestamp) (code location) someObjectName: dealloc
    >
    > II realize that a tool like this appears to be against your stated philosophy, Graham, but it sure would
    > have saved me and the Cocoa list a lot of time and frustration.

    This is precisely what you can do (among with many other things) using the Instruments tool, included with Xcode.

    Charles
  • On 2011 Feb 05, at 21:16, Charles Srstka wrote:

    >> During execution, I'd see the following:
    >>
    >> (timestamp) (code location) someObjectName: allocated
    >> (timestamp) (code location) someObjectName: init
    >> (timestamp) (code location) someObjectName: retained
    >> (timestamp) (code location) someObjectName: released
    >> (timestamp) (code location) someObjectName: released
    >> (timestamp) (code location) someObjectName: dealloc

    > This is precisely what you can do … using the Instruments tool….

    Which "Instrument" gives you a log precisely like that?
  • On Feb 6, 2011, at 6:35 AM, Jerry Krinock wrote:

    > On 2011 Feb 05, at 21:16, Charles Srstka wrote:
    >
    >>> During execution, I'd see the following:
    >>>
    >>> (timestamp) (code location) someObjectName: allocated
    >>> (timestamp) (code location) someObjectName: init
    >>> (timestamp) (code location) someObjectName: retained
    >>> (timestamp) (code location) someObjectName: released
    >>> (timestamp) (code location) someObjectName: released
    >>> (timestamp) (code location) someObjectName: dealloc
    >
    >> This is precisely what you can do … using the Instruments tool….
    >
    > Which "Instrument" gives you a log precisely like that?

    Object Allocations, with “Record Reference Counts” checked, will let you choose an object and show exactly where and when it was allocated, deallocated, retained, released, and even autoreleased. If you open the Extended Detail pane, you can get a full stack trace for each one of these occurrences, and you can double-click one of those frames to have it display the source file inline right in Instruments with the line at which the object was released, retained, etc. highlighted. Seems pretty much exactly what the OP wants.

    Charles
  • On 2011 Feb 06, at 07:44, Charles Srstka wrote:

    > Object Allocations, with “Record Reference Counts” checked, will let you choose an object and show exactly where and when it was allocated, deallocated, retained, released, and even autoreleased. If you open the Extended Detail pane, you can get a full stack trace for each one of these occurrences, and you can double-click one of those frames to have it display the source file inline right in Instruments with the line at which the object was released, retained, etc. highlighted.

    Thank you, Charles.  I've filed that advice away and hope that I never need to use it.

    I've had mostly negative experiences with Object Allocations – execution slowing to a crawl, consuming memory until, it can't get any more, then crashing.  Your description of all it does kind of explains why.  In contrast, my brute force method only logs for one class.

    But maybe since I have a big 64-bit Mac now with whoopee 4 GB of RAM, it will work better.
  • On Feb 6, 2011, at 9:18 PM, Jerry Krinock wrote:

    > On 2011 Feb 06, at 07:44, Charles Srstka wrote:
    >
    >> Object Allocations, with “Record Reference Counts” checked, will let you choose an object and show exactly where and when it was allocated, deallocated, retained, released, and even autoreleased. If you open the Extended Detail pane, you can get a full stack trace for each one of these occurrences, and you can double-click one of those frames to have it display the source file inline right in Instruments with the line at which the object was released, retained, etc. highlighted.
    >
    > Thank you, Charles.  I've filed that advice away and hope that I never need to use it.
    >
    > I've had mostly negative experiences with Object Allocations – execution slowing to a crawl, consuming memory until, it can't get any more, then crashing.  Your description of all it does kind of explains why.  In contrast, my brute force method only logs for one class.
    >
    > But maybe since I have a big 64-bit Mac now with whoopee 4 GB of RAM, it will work better.

    It works pretty well on my MBP, and is incredibly handy when you’re trying to track down a leak or a memory-related crash. To get the logs for retain, release, etc., though, you have to make sure to get info on the ObjectAlloc instance and check the “Record Reference Counts” box. Otherwise, you just get allocations and deallocations.

    Charles
previous month january 2011 next month
MTWTFSS
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            
Go to today