A note about UINavigationController and memory management

Discussion in 'iOS Programming' started by gormster, Mar 10, 2010.

  1. gormster macrumors newbie

    Joined:
    Jan 21, 2010
    #1
    Hi all, thought I'd relate this story since I saw, whilst googling for the answer, a lot of questions but no solutions.

    Say you've got a UINavigationController wrapped around a UITableViewController. If you created the table view controller from within Xcode, you've got a whole bunch of useful code already put there, including this gem:

    Code:
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    	// Navigation logic may go here. Create and push another view controller.
    	// AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil];
    	// [self.navigationController pushViewController:anotherViewController];
    	// [anotherViewController release];
    
    Here's the problem: when the user navigates away from this newly created view controller, it is not released. This becomes a serious problem if, like me, you have allocated something large, like an AudioQueue. (In my case, it was more annoying that the AudioQueue's stop and dispose functions were not being called in my dealloc method).

    SO. What's the deal? Well, the problem is, even though you release the view controller, the navigation controller retains it as well; hence, when you hit back, it's still got a retain count of 1. So, what you need to do is this:

    Code:
    AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil];
    [self.navigationController pushViewController:anotherViewController];
    [anotherViewController release];
    [anotherViewController autorelease]; // <-----
    
    This adds it to the autorelease pool created when you pushed that view controller, so it gets released properly.

    NOTE: This means you have to watch your memory management carefully! Make sure you release everything you retained, etc. I actually had to do some of this in viewDidDisappear: because of the situation with AudioQueues/NSTimers and threading/run loops.
     
  2. PhoneyDeveloper macrumors 68040

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #2
    Dude, you're wrong.

    If you push a view controller and then the user hits the back button the view controller is released by the nav controller. If it isn't being dealloced then you've got a memory error somewhere in your code.

    Don't violate the memory management rules in order to work around your bugs.

    I believe that there is a gotcha with the audio queues where they retain their delegate while they're playing. You need to stop the audio queue from playing and it will release its delegate. This may be your real problem.
     
  3. Huygir macrumors newbie

    Joined:
    Jul 7, 2010
    Location:
    East Coast U.S.
    #3
    I had this problem as well, and I just figured it out (so I'm editing my original rant :)...

    Internal to my map controller I am allocating and retain a "zoom controller" that uses the map controller as a delegate for communicating changes in zoom level (and vice versa). When I comment out the zoom controller allocation (or just the delegate reference) the [autorelease] for the view in the navcontroller is sufficient to get the proper behavior of dealloc when the view is popped. The problem is that this organization of delegates which makes perfect sense in this scenario creates a chicken-egg problem. The internal controller has a reference to the larger controller such that it will not be "dealloc'd" on pop. So the bi-directional delegate references have to be removed in some other method prior to the pop in order for the dealloc to occur.

    I'm sure this pattern causes all sorts of problems with complex objects/controllers, so be aware of it.

    *** Just using the double "release" technique in gormster's post will not cure the underlying problem (I don't believe) - I think it will simply result in a memory leak of the internal object, but I could be wrong. In any case, it's a bad idea because if you later remove the internal controller the double release will most definitely crash the app.
     
  4. PhoneyDeveloper macrumors 68040

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #4
    What you're describing is called a retain cycle. This is when two objects retain each other (directly or through a chain of objects). Since objects typically release their retained objects in their dealloc method, and since you have two objects that are retaining each other, the dealloc methods are never called.

    Avoiding retain cycles is why delegates are normally not retained.

    However, there are some system classes that do retain their delegates temporarily. The delegate is retained for a period of time while the object is doing work. It releases the delegate when the work is finished. In these cases there is a cancel or similar method that also causes the object to release its delegate. While this design is not the common one it isn't wrong. However, one can be caught out by it.

    You might need to add your own cancel method to your class or make sure that when the object is finished doing its work it releases whatever it needs to, rather than waiting for dealloc to do this, since dealloc may never come.
     

Share This Page