I have literally 24 IBOutlets in one view of a NavigationController (none are retained as properties). Is there a good way to release these? I have a feeling they are causing memory issues in slower 3G and 3GS devices.
No ARC
Any thoughts?
As you push UIViewControllers on to a UINavigationController, the view of the UIViewControllers which have been "pushed onto" may be unloaded to save memory (as they are invisible). The views are then reloaded when necessary (and you get the viewDidLoad callback). You should have IBOutlets to UIViewControllers not UINavigationController so that they can be released on viewDidUnload. The usual way to release them is to declare them as retained properties and set them to nil (with the self.outlet accessor) in viewDidUnload and dealloc. Or just use ARC.
ViewController.h
#interface ViewController : UIViewController
{
}
#property (retain, nonatomic) IBOutlet UILabel *myLabel;
#end
ViewController.m
#import "ViewController.h"
#implementation ViewController
#synthesize myLabel;
#pragma mark - View lifecycle
- (void)viewDidUnload
{
[self setMyLabel:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[myLabel release];
[super dealloc];
}
#end
Related
Update:
To clarify the context of the question; the question is not on how to avoid the problem, but to clarify what the piece of documentation means, as my experiment suggests that the views I was expecting to unload, based on the documentation, are not unloading. I would like to understand if this is a bug, or if I am misunderstanding the documentation. To solve the problem instead, I know that setting the images in viewWillAppear instead of viewDidLoad, and setting the images to nil in viewDidDisappear, is releasing the memory and the app doesn't crash. However, I would like to understand if the memory should have been released with the initial code, as the experiment is to simulate having such view controllers as outlets, and setting your UI images (backgrounds...) in Interface Builder, instead of setting them in code in viewWillAppear...
Original:
I am trying to understand some new aspect of iOS6, as documented in the View Controller Programming Guide:
On iOS 6 and Later, a View Controller Unloads Its Own Views When Desired
The default behavior for a view controller is to load its view hierarchy when the view property is first accessed and thereafter keep it in memory until the view controller is disposed of. The memory used by a view to draw itself onscreen is potentially quite large. However, the system automatically releases these expensive resources when the view is not attached to a window. The remaining memory used by most views is small enough that it is not worth it for the system to automatically purge and recreate the view hierarchy.
Let's say I am creating a simple app, with a rootViewController.
This rootViewController has a few child View Controllers, all declared as IBOutlets instead of being allocated in code.
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController1;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController2;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController3;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController4;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController6;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController7;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController8;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController9;
#property(nonatomic,strong) IBOutlet ChildViewController *childViewController10;
The rootViewController has a few buttons, pressing each of those does a simple presentModalViewController operation on each childViewController
-(IBAction)showChild1Action:(id)sender{
[self presentModalViewController:self.childViewController1 animated:true];
}
Each childViewController has a close button that dismisses the child view controller.
-(IBAction)closeAction:(id)sender{
[self dismissModalViewControllerAnimated:true];
}
My expectation from the documentation was that the child view controller view objects would be released from memory, as the child view controllers are dismissed.
However I purposely tested with large view objects, and running profile on such an app, the memory usage just keeps growing as each child controller gets presented, and the app eventually crashes after I present child controller #7 or so.
What is your understanding of what has changed in iOS6 in that aspect?
RootViewController.h
#import <UIKit/UIKit.h>
#import "ChildViewController.h"
#interface RootViewController : UIViewController
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController1;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController2;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController3;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController4;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController5;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController6;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController7;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController8;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController9;
#property(nonatomic,strong) IBOutlet ChildViewController *level2ViewController10;
-(IBAction)showChild1Action:(id)sender;
-(IBAction)showChild2Action:(id)sender;
-(IBAction)showChild3Action:(id)sender;
-(IBAction)showChild4Action:(id)sender;
-(IBAction)showChild5Action:(id)sender;
-(IBAction)showChild6Action:(id)sender;
-(IBAction)showChild7Action:(id)sender;
-(IBAction)showChild8Action:(id)sender;
-(IBAction)showChild9Action:(id)sender;
-(IBAction)showChild10Action:(id)sender;
#end
RootViewController.m
#import "RootViewController.h"
#interface RootViewController ()
#end
#implementation RootViewController
#synthesize level2ViewController1;
#synthesize level2ViewController2;
#synthesize level2ViewController3;
#synthesize level2ViewController4;
#synthesize level2ViewController5;
#synthesize level2ViewController6;
#synthesize level2ViewController7;
#synthesize level2ViewController8;
#synthesize level2ViewController9;
#synthesize level2ViewController10;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
-(IBAction)showChild1Action:(id)sender{
self.level2ViewController1.index=0;
[self presentModalViewController:self.level2ViewController1 animated:true];
}
-(IBAction)showChild2Action:(id)sender{
self.level2ViewController2.index=1;
[self presentModalViewController:self.level2ViewController2 animated:true];
}
-(IBAction)showChild3Action:(id)sender{
self.level2ViewController3.index=2;
[self presentModalViewController:self.level2ViewController3 animated:true];
}
-(IBAction)showChild4Action:(id)sender{
self.level2ViewController4.index=3;
[self presentModalViewController:self.level2ViewController4 animated:true];
}
-(IBAction)showChild5Action:(id)sender{
self.level2ViewController5.index=4;
[self presentModalViewController:self.level2ViewController5 animated:true];
}
-(IBAction)showChild6Action:(id)sender{
self.level2ViewController6.index=5;
[self presentModalViewController:self.level2ViewController6 animated:true];
}
-(IBAction)showChild7Action:(id)sender{
self.level2ViewController7.index=6;
[self presentModalViewController:self.level2ViewController7 animated:true];
}
-(IBAction)showChild8Action:(id)sender{
self.level2ViewController8.index=7;
[self presentModalViewController:self.level2ViewController8 animated:true];
}
-(IBAction)showChild9Action:(id)sender{
self.level2ViewController9.index=8;
[self presentModalViewController:self.level2ViewController9 animated:true];
}
-(IBAction)showChild10Action:(id)sender{
self.level2ViewController10.index=9;
[self presentModalViewController:self.level2ViewController10 animated:true];
}
#end
ChildViewController.h
#import <UIKit/UIKit.h>
#interface ChildViewController : UIViewController
#property(nonatomic,weak) IBOutlet UIImageView *image1;
#property NSInteger index;
-(IBAction)closeAction:(id)sender;
#end
ChildViewController.m
#import "ChildViewController.h"
#interface ChildViewController ()
#end
#implementation ChildViewController
#synthesize image1;
#synthesize index;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
if(self.index==0)
[self.image1 setImage:[UIImage imageNamed:#"IMG1.JPG"]];
if(self.index==1)
[self.image1 setImage:[UIImage imageNamed:#"IMG2.JPG"]];
if(self.index==2)
[self.image1 setImage:[UIImage imageNamed:#"IMG3.JPG"]];
if(self.index==3)
[self.image1 setImage:[UIImage imageNamed:#"IMG4.JPG"]];
if(self.index==4)
[self.image1 setImage:[UIImage imageNamed:#"IMG5.JPG"]];
if(self.index==5)
[self.image1 setImage:[UIImage imageNamed:#"IMG6.JPG"]];
if(self.index==6)
[self.image1 setImage:[UIImage imageNamed:#"IMG7.JPG"]];
if(self.index==7)
[self.image1 setImage:[UIImage imageNamed:#"IMG8.JPG"]];
if(self.index==8)
[self.image1 setImage:[UIImage imageNamed:#"IMG9.JPG"]];
if(self.index==9)
[self.image1 setImage:[UIImage imageNamed:#"IMG10.JPG"]];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
-(IBAction)closeAction:(id)sender{
[self dismissModalViewControllerAnimated:true];
}
When using Instruments I get these observations:
The memory allocation instrument is showing what I would expect to see: total allocation that goes up when child view controller is presented, goes down when child view is dismissed
However the activity indicator is telling a different story, with real memory increasing with every presentModalViewController, and never decreasing when dismissing them
The answer I eventually got back from Apple:
Your "large resources" happen to be images that you've loaded cached
via +imageNamed:. Because they are loaded cached, they are exempted
from the automatic cleanup. Generally only content generated via
-drawRect: or by Core Animation is automatically released here. Because your views continue to exist, they continue to hold a
reference to these cached images, and we can't purge them on memory
warning either
Looks like cached resource isn't part of the automatic cleanup mentioned in the doc, and those resources only get deallocated when they stop being referenced.
Sounds like you have a leak somewhere. Even if the system isn't releasing those views, memory use should max out after you've presented all 10 child controllers. If memory use grows without bound, your child controllers are probably giving up their views (and so creating new ones each time they're presented), but the views aren't being deallocated -- classic symptom of an over-retain situation. Try cutting the number of child controllers down to two and see if the same thing eventually happens. Or, use Instruments to look for a leak.
Update: -[UIImage imageNamed] has a reputation for never releasing the images it loads -- that may be the reason for your memory growth. Try loading the images using a different method, or not at all (since they're not really important to the experiment).
Instead of using imageNamed: use initWithContentsOfFile.
For example:
__weak NSString *filePath = [[NSBundle mainBundle] pathForResource:#"image" ofType:#"png"];
self.imageView.image = [[UIImage alloc] initWithContentsOfFile:filePath];
Because alloc method is being used, GC is marking this object. You can even mark the filePath string for GC cleanup.
I have been working on an app, and the book I read said to put these statements into the viewDidUnload and dealloc methods. What other information should go here? I have buttons and labels in my program. Do I need to do anything for them?
I want an efficiently running application.
Here's my code:
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.doublePicker = nil;
self.color = nil;
self.choice = nil;
[super viewDidUnload];
}
- (void)dealloc {
[doublePicker release];
[color release];
[choice release];
[super dealloc];
}
You should only release your IBOutlets and other UI elements in viewDidUnload. All the other data that you allocated in your view controller (as well as the IBOutlets) should be released in the dealloc method. That's because the view can be loaded and unloaded multiple times during the lifetime of a view controller. For example, a view can be unloaded if it is not visible, but the data behind it (in the view controller) still needs to be kept in memory. When both the view and its controller are no longer needed, the dealloc method is called.
The code you posted is correct, but you should also create properties for your outlets, like so:
in your .h file:
#property (nonatomic, retain) UIPickerView *doublePicker;
#property (nonatomic, retain) UIPickerView *color;
// ...etc
in your .m file:
#synthesize doublePicker;
#synthesize color;
// ...etc
There is some debate about this technique (explicitly retaining your outlets and releasing them like this), but it is the technique that Apple recommends:
http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmNibObjects.html#//apple_ref/doc/uid/TP40004998-SW2
The general rule is that you want to call release on anything that you alloc or init. Components that you create in xib's do not need to be released.
The question is if insertSubview is retaining the views and if I'm doing it right.
I would say yes. Since I'm not using the property anymore I don't receive an EXC_BAD_ACCESS. I think when releasing the view all subviews are also released. And so mapView is over-released. I'm right or do I still have a memory management issue?
My ViewController.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface MapViewController : UIViewController <MKMapViewDelegate> {
MKMapView *mapView;
// ...
}
//#property (nonatomic, retain) MKMapView *mapView;
// ...
#end
My ViewController.m
#import "MapViewController.h"
#implementation MapViewController
//#synthesize mapView;
- (void)viewDidLoad {
[super viewDidLoad];
//self.mapView=[[[MKMapView alloc] initWithFrame:self.view.bounds] autorelease];
mapView = [[MKMapView alloc] initWithFrame:self.view.bounds];
[self.view insertSubview:mapView atIndex:0];
[mapView release];
// ...
}
- (void)dealloc {
//[mapView release];
[super dealloc];
}
#end
- (void)dealloc {
//[mapView dealloc];
[super dealloc];
}
You should never call dealloc directly (save for [super dealloc]; at the end of the method). That will most assuredly cause a crash in most situations.
Since that isn't the source of your crash, you have an over-release somewhere. Use Instrument's Zombie Detection to figure out where.
Yes, you are correct on all counts:
the call to insertSubView: should be retaining the mapView that you are passing it.
releasing your reference to the mapView after you add it to the parent view
the parent view will release all the retained subviews when it is released
As a rule, you should not worry about whether or how another object will retain an instance you give it. That's up to that object to deal with; you only have to worry about making sure an instance that you intend to directly access later is retained. Don't rely on another object to keep an instance retained for you.
In your example, you have an instance (mapView) which is accessible to MapViewController but MapViewController does not have it's own retention for it. self.view could release mapView at any time for any number of reasons and you'd suddenly have bad memory there.
I am creating my first tab controller app. I have 2 tabs with 2 UIViews in them. I did this mostly from Interface Builder all I did in Xcode was add 2 files firstControllerView and SecController view. I can see the tab controller is working went I run the app (I simply changed the background color on the 2 UIViews in the tabs to see the effect).
Now I want to add a label to the secondView and set its text programmatically from code. This is whats breaking for me! I am doing something wrong. In my SecondViewController.h it looks like this:
#interface SecondViewController : UIViewController {
IBOutlet UILabel *title;
}
#property (nonatomic,retain) UILabel *title;
#end
and the .m looks like this...
#import "SecondViewController.h"
#implementation SecondViewController
#synthesize title;
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[title setText:#"Hello Nick"];
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[title release];
[super dealloc];
}
#end
After this I went back to Interface Builder and dragged the outlet reference to the label. When I run the simulator it crashes.
What am I missing here? It must be something simple.
Forgot to create an outlet for a tabbarcontroller in the app delegate then connect that outlet to the tabbar controller in interface builder.
Do you have to release IBOulets in dealloc? I'm not sure on this one because I didn't do the alloc and typically you only release for something you called alloc on. Anyone know?
Your IBOutlets are probably #properties. If they are, and you have retain as an attribute, then you do need to release in -dealloc
In other words:
#interface MyViewController : UIViewController {
IBOutlet UITableView *myTable;
}
#property (nonatomic, retain) IBOutlet UITableView *myTable;
#end
You will have to [myTable release]; in your dealloc.
If you make a new Navigation Based App in Xcode, and look in the appdelegate.h:
#interface Untitled1AppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UINavigationController *navigationController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#end
and the dealloc for appdelegate.m:
- (void)dealloc {
[navigationController release];
[window release];
[super dealloc];
}
The key thing to see here are lines like these:
#property (nonatomic, retain) IBOutlet UIWindow *window;
If there is a retain in there, that means that the property is being "owned" by your code and you have to release it.
Of course, there are other ways, such as not declaring the IBOutlets as properties, or declaring them as properties without retaining. I find that in most cases I prefer to have them be retained properties, which I then have to explicitly release. An example of this is when you flip from one view controller to another. As one view controller is dismissed, its views are removed and they are released. Any IBOutlet UILabels on that view would get released too if I don't have them retained. That means that when I flip back to the old view, I have to go through and reset my labels and controls to their last values, when I could have easily kept them saved if I just retain the IBOutlet.
If you just use IBOutlet in your interface, you DO NOT need to release them. Reason being is that unless you explicitly retain them in your code, they are merely being set. They stick around because the view is there. Obviously, if you also use properties and retain them, you need to release on dealloc.
It's not about IBOutlet, it's about your declaration.
If you use a new project wizard in Xcode, you probably get some code like this in your header file.
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
You can see, there's retain keyword in the header file. Following the memory management guideline, you MUST release everything your retain (by calling alloc, copy, retain, etc.). And you have retain in your code then you must release it.
Additionally, the wizard already add some release code for you.
- (void)dealloc {
[tabBarController release];
[window release];
[super dealloc];
}
As you said, you should release everything that you allocated yourself (with alloc or copy). It works the other way: you should not release any Cocoa objects you did not allocate yourself (some CoreFoundation functions allocate, and you're responsible for releasing them, but it's not the case here).
If you didn't allocate your IBOutlet, then you don't have to release it, unless of course, for some reason, you retained it somewhere.
To answer the side question by Joe D'Andrea. You can use self.label = nil;. Because it is calling setLabel, which is auto generated:
- (void)setLabel:(UILabel *)input
{
[label autorelease];
label = [input retain];
}
As you can see the current label will be released then nil is assigned to label.
But make sure you don't write it as label = nil. That will not work.
Because you need to call the auto generated label accessor method.
Here's what I have been doing with regard to IBOutlet objects (in conjunction with a NIB file):
#interface MyViewController : UIViewController {
UILabel *label;
}
#property (nonatomic, retain) IBOutlet UILabel *label;
#end
#implementation MyViewController
#synthesize label;
- (void)setView:(UIView *)aView {
if (!aView) {
// view is being set to nil
// set outlets to nil to get the benefit of the didReceiveMemoryWarning!
self.label = nil;
}
// Invoke super's implementation last
[super setView:aView];
}
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.label = nil;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)dealloc {
[label release];
[super dealloc];
}
Side question: Does it make more sense to use self.label = nil in dealloc, or must release be explicitly called (for instance, to keep the static analyzer happy)?
I suppose, at that point, we're on our way out anyway, so there's no need to set our IBOutlet objects to nil.
If this is your BlahViewController.h:
// BlahViewController.h
#import <UIKit/UIKit.h>
#interface BlahViewController
{
IBOutlet UINavigationBar *navigationBar;
}
#property (nonatomic, retain) IBOutlet UINavigationBar *navigationBar;
#end
Then this would be your dealloc in BlahViewController.m:
- (void)dealloc
{
[navigationBar release];
[super dealloc];
}
However, if this is your BlahViewController.h:
// BlahViewController.h
#import <UIKit/UIKit.h>
#interface BlahViewController
{
IBOutlet UINavigationBar *navigationBar;
}
#end
Then this would be your dealloc in BlahViewController.m:
- (void)dealloc
{
[super dealloc];
}
And finally, if this is your BlahViewController.h:
// BlahViewController.h
#import <UIKit/UIKit.h>
#interface BlahViewController
{
IBOutlet UINavigationBar *navigationBar;
IBOutlet MKMapView *map;
}
#property (nonatomic, retain) IBOutlet UINavigationBar *navigationBar;
#end
Then this would be your dealloc in BlahViewController.m:
- (void)dealloc
{
[navigationBar release];
[super dealloc];
}
In short, if you declare it as a property, with retain, then you need to release it.