14

I have been under the assumption for a while that viewDidUnload is always called when a controller is deallocated. Is this a correct assumption?

I've just been exploring some odd things, and set a breakpoint in my controller's viewDidUnload and it's dealloc. It appears that dealloc is called, but the viewDidUnload method is never called. I even added a self.view = nil to my dealloc and it still didn't seem to call it.

Does this mean that retained view objects I have been releasing in the viewDidUnload method also need to be released in my dealloc method to be sure they really go away?

I know there are many other questions on StackOverflow about viewDidUnload, but none specifically address this issue about duplication of release statements between the 2 methods.


A more concrete exmaple in a fresh project on the 3.1.2 SDK:

@implementation TestViewController

@synthesize label;

- (IBAction)push {
    TestViewController *controller = [[[TestViewController alloc] initWithNibName:@"TestViewController" bundle:nil] autorelease];
    [self.navigationController pushViewController:controller animated:YES];
}

- (void)viewDidUnload {
    self.label = nil;
    NSLog(@"viewDidUnload was called");
}

- (void)dealloc {
    [super dealloc];
    NSLog(@"label retain count: %i", [label retainCount]);
}

@end

My app delegate creates a simple navigation controller with one of these as it's root controller. When I tap the button linked to push 3 times, and then hit the back button three times, the following output is generated.

ViewDidUnloadTest[2887:207] label retain count: 2
ViewDidUnloadTest[2887:207] label retain count: 2
ViewDidUnloadTest[2887:207] label retain count: 2

Which is 2 higher that I would think it would be. Retained once by the view and once by the controller. But after the dealloc I would have expected the view to be gone releasing my label, and the controller to be gone calling viewDidUnload and releasing it. Although there may be an autorelease in there throwing off the count at this point.

But at least it's clear that viewDidUnload is not getting called at all, which contrary to this answer here: Are viewDidUnload and dealloc always called when tearing down a UIViewController?

Perhaps I should simply call [self viewDidUnload] in all my dealloc methods on controllers? Worse than can happen is that I set a property to nil twice, right?

Community
  • 1
  • 1
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337

1 Answers1

20

Unless you need to break a retain cycle, you should generally only be releasing objects in your dealloc method. viewDidUnload is an exception; it is invoked in low memory situations and should be used to release anything that you can.

If you do need to release them anywhere else, then always set the reference to nil after the release. That'll protect your app from blowing up later (likely in dealloc).

Note that the documentation quite explicitly calls out that the view property will already be nil when viewDidUnload is called.

bbum
  • 162,346
  • 23
  • 271
  • 359
  • So you are saying "Yes, you need to release retained properties in your `dealloc` even if you release and nullify them in your `viewDidUnload` method"? – Alex Wayne Mar 01 '10 at 01:44
  • 3
    Exactly; viewDidUnload is for low memory situations & dealloc is for cleanup. The two are orthogonal. – bbum Mar 01 '10 at 05:21
  • 1
    This is a very confusing point coming from the examples! Most examples show things being set to nil in the viewDidUnload. What it fails to explain is that after something is set to nil, you can't release it and expect it to deallocate! Without taking note of this, you'll end up with memory leaks all over the place. Thanks for your helpful post. – jocull Nov 06 '10 at 00:31
  • 4
    @jocull Setting a retained property to nil will in fact release it, which will make it deallocate as long as you don't have another retained reference somewhere else. – Nick Forge Feb 02 '11 at 06:07
  • @Nick I couldn't get it to happen that way. Can you point me at any documentation that further explains this? – jocull Feb 02 '11 at 06:34
  • 2
    That's the whole point of a retained property - when you call `setFoo:`, the old `foo` value gets released, and the new one gets retained. Read about the `retain` property attribute here: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocProperties.html%23//apple_ref/doc/uid/TP30001163-CH17-SW2 – Nick Forge Feb 02 '11 at 06:40
  • @Nick Does this only happen if you use `[setFoo:nill];` or does it also work if you use `somevar.Foo = nil;` ? – jocull Feb 03 '11 at 18:44
  • @jocull `[foo setBar:nil];` will be compiled to exactly the same binary as `foo.bar = nil;` - they are exactly equivalent (as long as foo has a `setBar:` method declared). – Nick Forge Feb 03 '11 at 23:32
  • Note that [foo setBar:nil] and foo.bar = nil both release a retained property, bar = nil (if bar is local to the call) will NOT and you will have a leak. You would need to do [bar release], bar = nil; in that case. – Cory Imdieke Feb 15 '11 at 23:37
  • 4
    Yup -- and `[bar release];` without `bar = nil;` happily creates a dangling pointer! – bbum Feb 16 '11 at 00:26
  • I have create a object of class and release and set nil it. and re alloc init it. . . . but the released object not kill it task. how i kill all the tasks started by the object. – Amit Battan Aug 01 '12 at 12:55