Running Instruments on my iPad app found 2 leaks, except I cannot understand where they are coming from. The first one is in this method in my app delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window addSubview:self.viewController.view]; // <--- it leaks on this line
[window makeKeyAndVisible];
return YES;
}
I don't know why this is leaking, I am releasing viewController in dealloc. The second leak is in one of my table view controllers in this section of code:
EditLocationViewController *locationController = [[EditLocationViewController alloc] initWithLocation:self.location];
[self.navigationController pushViewController:locationController animated:YES]; // <--- it leaks on this line
[locationController release];
I went through my EditLocationViewController class and made sure that all retained properties are being released, etc. so I can't see a reason why it would leak.
Either I'm missing something here or Instruments is reporting false positives.
What makes you think it leaks there? My guess is the view itself is being leaked in both cases (the one belonging to the controller that you're making visible), or one of the ancillary views that's loaded as part of the view controller's -loadView or -viewDidLoad (this includes views loaded from a nib and attached to an outlet in the view controller).
If you're using the IBOutlet declaration on your ivars, this could very well be the case, as those ivars will be retained by the view controller. In that case you need to release them in -viewDidUnload as well as in -dealloc (be sure to nil them out after releasing them in -viewDidUnload or you'll crash the next time you access them).
Related
I have a UINavigationController object (named LoginNav) which consists of ViewController1 & ViewController2, my iPad app starts by loading a UISplitViewController subclass (named mainSplitViewController) and then presenting LoginNav modally on top of it (this is by the way done in didFinishLaunchingWithOptions method of AppDelegate like this:
[self.mainSplitViewController presentModalViewController:LoginNav animated:YES];).
Once ViewController1 is shown, I tap a UIButton in it to push ViewController2, when I finish working in ViewController2 I tap a UIButton in it to call [self.navigationController dismissModalViewControllerAnimated:YES]; to dismiss LoginNav with both of its view controllers and show mainSplitViewController's contents.
There is a dealloc method in both ViewController1 & ViewController2 with NSLog statement in each one, once loginNav is dismissed, the NSLogs never get fired, but doing [self.navigationController.viewControllers objectAtIndex:0] release]; & [self.navigationController.viewControllers objectAtIndex:1] release]; right after [self.navigationController dismissModalViewControllerAnimated:YES]; fires both NSLogs.
I commented out the above two release statements, then I launched the Allocations instrument, and launched the app again and pushed ViewController2 then dismissed loginNav as described above, and looked at Live Bytes column (All Allocations value ) it was 6.9 MB right after the dismissal of loginNav, then I did this step again but in this case using the two release statements, I got exactly a 6.9 MB value on Live Bytes column.
Two Questions:
1) why do not the dealloc methods of ViewController1 & ViewController2 never get fired after the dismissal of the navigation controller LoginNav that holds them ? and is it correct to do the above two release statements to release these view controllers ?
2) why releasing ViewController1 & ViewController2 does not free up memory ?
p.s. there is no single variable (or IBOutlet) being held in memory in both ViewController1 & ViewController2, everything is released in both of them.
These kinds of issues are nearly impossible to troubleshoot without seeing all your code. When you manage memory manually, there are multiple areas that you can go wrong. For example the following code will leak:
- (void)didSelectSomethingInViewControllerOne
{
ViewController2 *vc2 = [[ViewController2 alloc] init];
[self.navigationController pushViewController:vc2 animated:YES];
}
In this case you have allocated the object and thus have ownership of it. Then the nav controller takes ownership of it. When you pop the controller from the navigation stack, the navigation controller relinquishes ownership of it, but you never did, so it still has a retain count of 1 and won't get deallocated.
Relinquishing ownership of the controllers later in your code (like after dismissing the modal view) is a bad idea. It makes it difficult to analyze ownership when your releases are all over the place. As soon as the navigation controller has ownership you can release the object you allocated, as you do not intend to use it in the future:
- (void)didSelectSomethingInViewControllerOne
{
ViewController2 *vc2 = [[ViewController2 alloc] init];
[self.navigationController pushViewController:vc2 animated:YES];
[vc2 release];
}
The situation above can have nothing to do with your problem. Your problem may reside in many different areas. Which is why troubleshooting memory management problems is difficult. Without seeing source code.
Consider transitioning your project to ARC:
http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
Another thing you might have fallen foul of is a retained property (such as a delegate) in your LoginNav.
Because there are multiple situations in which I would want to pop a view controller from the navigation stack, I have one method that does it and it is called from three different places.
- (void)dismissSelfCon {
NSLog(#"dismiss");
[locationManager stopUpdatingHeading];
[locationManager stopUpdatingLocation];
locationManager.delegate = nil;
mapView.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[[Trail_TrackerAppDelegate appDelegate] navCon] popViewControllerAnimated:YES];
}
In one situation, if the mapView has an annotation placed on it (I'm not sure if that is the defining characteristic, but I think it is), this method is called (and I am sure that it is called because #"dismiss" is printed to the console), but the location manager does not stop sending location updates! Also, because the delegate is not set to nil, the app crashes because the view controller receives respondsToSelector: from one of the objects of which it is a delegate.
How is this possible?
The most likely cause of this is that locationManager at this point is nil. First rule: always use accessors; don't directly access your ivars except in init and deallloc.
My suspicion from your description would be that this object (the one with dismissSelfCon) doesn't clear locationManager.delegate during dealloc, and that you're being deallocated without calling dismissSelfCon.
The solution was this:
The way I have my view controller set up (which is a little strange I know, and is something I'm trying to change/fix if you will see my question here: Can't allocate CLLocationManager), the CLLocationManager is being allocated, delegate set, etc in viewDidAppear. I present a MFMessageComposeViewController during the app, and when it gets dismissed, viewDidAppear is called again, re-allocating the CLLocationManager and causing my problem. With a little boolean magic, I adjusted the viewDidAppear code so that the CLLocationManager is only set up and allocated one time.
This may sound a newbie question ... but however I'm new to iOS development.
I've created UITabController object programmatically like this.
mTabBarController = [[UITabBarController alloc] init];
...
mTabBarController.viewControllers = [NSArray arrayWithArray:tabBarItems];
[tabBarItems release];
And releasing mTabBarController in dealloc like this.
- (void)dealloc {
[mTabBarController release];
}
Now my question : will I get a memory leak ? When I assign value t viewController the ref count of tabBarItems is still 1. When I release mTabBarController does it also release all its viewcontrollers ?
Yes, the tab controller owns an array of view controllers (and everything in an array is retained). You're not creating a leak as long as you properly release or autorelease the items you're adding to the tabBarItems array.
It really helps to think of object relationships as ownerships.
UITabBarController should never be placed as the child of another ViewController, so you will always have to release it in dealloc. If your TabBarController's view is the childview of your application's window, it's ok not to release it in dealloc since the only time dealloc will ever get called is when your program is closing, in which case your controller will be released anyways. However, some people like to release it in dealloc anyways just to keep their code consistent. What you're doing is fine.
I observed that viewDidLoad() is called before didFinishLaunchingWithOptions() and I am looking for something where I can put some initialization code that has to be called before viewDidLoad().
Is there such a place?
Also, it is acceptable to recall viewDidLoad() from other place. It should be ok, or too risky?
You are wrong.
Place a NSLog directly under the method header and you will see that ViewDidLoad is directly called after.
[self.window addSubview:self.yourViewController.view];
So, you either use viewDidLoad or alternatively and not really beautiful you could use.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
This even gets called before ViewDidload
There's
- loadView()
Kicks in before viewDidLoad() and comes with an advice to never be called directly after that.
Here's the link to the apple docs.
I had a similar problem once that was caused when I added the view controller I wanted added to the window using the MainWindow.xib file.
To get around this, I assigned the window's rootViewController (you can also call addSubView, but assigning the rootViewController is better) in the didFinishLaunchingWithOptions: method of the app delegate. Once you do this you can easily put whatever logic you want in front or behind where this happens. It puts you in full control of when your view controller loads. In contrast, when the view controller is loaded via the nib, it's difficult to execute code in front of it (if at all possible). I know you sepecify the main xib in the app's plist, but I don't know if there is a way to run code before that nib is loaded.
In general I avoid adding the view controller in the xib for this reason.
My code looks more like:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// special pre load logic here...
UIViewController *myVC = [[MyAwesomeViewController alloc] init];
self.window.rootViewController = myVC;
[myVC release];
// special post load logic here...
[self.window makeKeyAndVisible];
return YES;
}
You may do your app initializations in viewDidLoad, however don't do any boundaries or size settings here, as they are not yet set.
Do them in viewDidLayoutSubviews, which is called after viewDidLoad.
I found this article, which was very helpful to me:
The UIViewController lifecycle
You could put initialization code in the init method of the class.
And it's fine to call viewDidLoad again from elsewhere. It's just like any other method.
EDIT:
It's fine to call viewDidLoad - but you should be careful with memory management. If you're allocating objects in viewDidLoad, calling it again will cause leaks. So, because of the typical functionality of viewDidLoad, you might want to pull the code out into another method that you'll call repeatedly and call from viewDidLoad.
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?
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.