views:

4408

answers:

3

I ran into a situation that seems to suggest otherwise. In the following code snippet, if I remove the line: self.navigationController = nav, the root controller's view won't show up, suggesting to me that addSubview might not actually retain the view as otherwise suggested. Any idea?

- (void)applicationDidFinishLaunching:(UIApplication *)application {   
   self.testViewController = [[TestViewController alloc] initWithNibName:@"TestView" bundle:  [NSBundle mainBundle]];

   UINavigationController *nav = [[UINavigationController alloc]  initWithRootViewController:self.testViewController];

   self.navigationController = nav;  //<-- if this line is removed, test view won't show up

   [window addSubview:nav.view];

   [nav release];
}
A: 

This doesn't look lika a retain/release question to me. You view won't show up if you comment out self.navigationController = nav; because then in the next line, [window addSubview:self.navigationController.view] your self.navigationController property won't be set. It's probably nil or it would crash but can't say for sure without more of the code.

monowerker
I have updated the code back to the original code when things didn't work and I had to introduce self.navigationController into things to hold a reference to nav. Also, what you see above is all the code there is and nothing else.
Boon
In objective-c, ivars start off with the value 0. So, self.navigationController would just be nil.
Jon Hess
+1  A: 

The problem probably isn't that the view isn't retained, it's that the controller isn't retained.

Without this line:

self.navigationController = nav

Nothing is retaining the navigation controller. It would be strange to have the view outlive the controller.

Jon Hess
A view is not "magically" tied to a view controller. A view doesn't just die because its view controller dies. If the view has been retained elsewhere, it will absolutely outlive any view controllers.
Corey Floyd
I didn't say it wouldn't happen. I said it would be strange for one to outlive the other. Especially when the view is the navigation controller's view, which is some internal view hierarchy consed up by the navigation controller.
Jon Hess
If you follow convention, you are right. My only point is that it is not a reason in and of itself. The only reason this tends to be true is because you generally do not release a view controller before removing its view from the view hierarchy. But there are no mechanisms in UIViewController to keep you from doing this, it just stems from the practice of proper memory management.
Corey Floyd
Jon's is the most accurate answer. While nav.view is retained by the window, nav itself gets released and deallocated. Thus, the nav controller never gets around to putting anything interesting in its view (like displaying the root view controller).
Daniel Dickison
A long time ago, but nav does not get released and deallocated. it s actually released. The reference to nav is lost outside of the scope of the method, however. Which means the Cocoa rendering engine will have an invalid pointer when it attempts to draw the view in a future run loop.
Corey Floyd
+10  A: 

This line:

[window addSubview:nav.view];

does NOT add a view to the screen immediately. It is displayed by the OS in some future run loop on a possibly different thread. The actual implementation we can't be sure of.

This is why Apple defines delegate methods like viewDidAppear/viewWillAppear, otherwise we would not need them as we would know precisely when these events occur.

Moreover, adding a subview as you said, does indeed retains the view. It does NOT however retain the view controller or the navigation controller. Since the navigation controller WILL retain any added view controllers, we do not have to back them with an ivar.

But, your reference to the navigation controller must persist beyond the scope of the method. or depending on your code it could be dealloc-ed or have its reference lost.

So you must keep a reference to the navigation controller with an ivar and set it like so:

self.navigationController = nav; 

So even though nav.view contains a pointer to testViewController.view, the application has no reference the navigation controller and, by extension, the view. The result is a blank screen.


To make this more obvious that it isn't a retain/release problem, you are actually leaking in the following method:

self.testViewController = [[TestViewController alloc] initWithNibName:@"TestView" bundle: [NSBundle mainBundle]];

You need to autorelease to balance out your retain/releases by:

self.testViewController = [[[TestViewController alloc] initWithNibName:@"TestView" bundle: [NSBundle mainBundle]] autorelease];

So, that means your view has never, ever been deallocated any time you have ran this code. Which further assures us that your issue is indeed a lost reference.

Corey Floyd
Nice work Corey, really like the explanation you laid out.
Boon
It's incorrect that the _view_ will be deallocated before the window retains it. If that were the case, you'd see an exception when the window tries to access the deallocated object. What does get deallocated is the nav controller, which prevents it from populating its view with interesting content (like the nav bar and root view).
Daniel Dickison
After thinking about it more I added some additional info which also helps explain what you see.
Corey Floyd
Thanks, I misspoke my self I meant the navigation controller, I'll update.
Corey Floyd
Hopefully this is a better answer
Corey Floyd
I think I was getting confused by your use of "view" -- I was referring to the nav controller's view (which does get retained and displayed by the window), while you're referring to the root controller's view (i.e. testViewController.view) which never gets displayed.
Daniel Dickison
I was playing loose and fast with the term "view", which in this context can mean several very different things. I tried to be a little more precise with my revision.
Corey Floyd