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.
Related
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
I need to view a subview with an activity indicator.
This is my code but the subview doesn't appear:
#interface ProgressViewController : UIViewController {
IBOutlet UIActivityIndicatorView *myActivityIndicator;
}
#property (nonatomic, retain) IBOutlet UIActivityIndicatorView *myActivityIndicator;
#end
#implementation ProgressViewController
#synthesize myActivityIndicator;
- (void)viewDidLoad {
[myActivityIndicator startAnimating];
[super viewDidLoad];
}
- (void)viewWillDisappear:(BOOL)animated {
[myActivityIndicator stopAnimating];
}
#end
#import "ProgressViewController.h"
#interface MyViewController : UIViewController {
ProgressViewController *progressViewController;
}
#property (nonatomic, retain) ProgressViewController *progressViewController;
#end
#implementation MyViewController
#synthesize progressViewController
- (void)viewDidLoad
{
progressViewController = [[ProgressViewController alloc] initWithNibName:#"ProgressViewController" bundle:nil];
[self.view addSubview:progressViewController.view];
sleep(4);
[progressViewController.view removeFromSuperview];
[super viewDidLoad];
}
#end
There could be several causes, and it's still a bit unclear from the code you sent, which one it is.
First, you shouldn't use sleep(4) in your code - it messes up the application engine iOS runs to support user input, screen refresh, etc.
Your code could easily be changed to:
[self performSelector:#selector(removeMyProgressView:) withObject:progressViewController.view afterDelay:4.0];
and have removeFromSuperview in your removeMyProgressView: function.
Also, this line of code is buggy:
progressViewController = [[ProgressViewController alloc] initWithNibName:#"ProgressViewController" bundle:nil];
It should be
self.progressViewController = [[ProgressViewController alloc] initWithNibName:#"ProgressViewController" bundle:nil];
Otherwise you don't call the setter function (#sythesized property), and the object isn't retained. It could be that it is released, and therefore you don't see it.
If this none of this is right, we'll keep pounding at it :)
Good luck!
Oded.
Everything in your -viewDidLoad method happens in one runloop. This means that you add and remove the activity indicator without giving the system a chance to actually draw it. The 4 seconds of sleep don't help. Those just make the runloop take longer to finish.
call [super viewDidLoad] before anything in - (void)viewDidLoad methods
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.
I have three view controllers, one root controller, one login view controller and one customers view controller. I want to pass the entered username and password in login view controller to the customers view controller. My files and code is displayed below, could you please guide me, how can access to variables set in the login view controller? Or how can I pass variables to customers view controller?
I have these class files:
/classes/MySoftwareAppDelegate.h
/classes/MySoftwareAppDelegate.m
/classes/ViewController.h
/classes/ViewController.m
/classes/LoginController.h
/classes/LoginController.m
/classes/CustomersController.h
/classes/CustomersController.m
I have these views:
/resources/MainWindow.xib
/resources/Login.xib
/resources/Customers.xib
In the AppDelegate, I have successfully inserted the sub view "Login" and it's displayed whenever the app starts.
In the login view, I enter my username and password and then click the "Login" button. When this button is clicked, an IBAction is triggered. In this IBAction, I want to change the current subview with the Customers.
Here's the code I have used:
MySoftwareAppDelegate.h
#import <UIKit/UIKit.h>
#class ViewController;
#interface MySoftwareAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
ViewController *viewController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet ViewController *viewController;
#end
MySoftwareAppDelegate.m
#import "MySoftwareAppDelegate.h"
#import "ViewController.h"
#implementation MySoftwareAppDelegate
#synthesize window;
#synthesize viewController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after application launch
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}
- (void)dealloc {
[viewController release];
[window release];
[super dealloc];
}
#end
ViewController.h
#import <UIKit/UIKit.h>
#class LoginController;
#interface ViewController : UIViewController {
LoginController *loginController;
}
#property (nonatomic, retain) LoginController *loginController;
#end
ViewController.m
#import "ViewController.h"
#import "LoginController.h"
#implementation ViewController
#synthesize loginController;
- (void)viewDidLoad {
LoginController *tmpViewController = [[LoginController alloc] initWithNibName:#"Login" bundle:nil];
self.loginController = tmpViewController;
[self.view insertSubview:loginController.view atIndex:0];
[tmpViewController release];
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
if (self.loginController.view.superview == nil) {
self.loginController = nil;
}
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[loginController release];
[super dealloc];
}
#end
LoginController.h
#import <UIKit/UIKit.h>
#class CustomersController;
#interface LoginController : UIViewController {
UIButton *loginButton;
UITextField *usernameTextField;
UITextField *passwordTextField;
NSMutableString *available_credits;
NSString *current_xml_element;
CustomersController *customersController;
}
#property (nonatomic, retain) IBOutlet UIButton *loginButton;
#property (nonatomic, retain) IBOutlet UITextField *usernameTextField;
#property (nonatomic, retain) IBOutlet UITextField *passwordTextField;
#property (nonatomic, retain) NSMutableString *available_credits;
#property (nonatomic, retain) NSString *current_xml_element;
#property (nonatomic, retain) CustomersController *customersController;
-(IBAction)textFieldDoneEditing:(id)sender;
-(IBAction)backgroundTap:(id)sender;
-(IBAction)loginToAccount:(id)sender;
#end
LoginController.m
#import "LoginController.h"
#import "CustomersController.h"
#implementation LoginController
#synthesize loginButton;
#synthesize usernameTextField;
#synthesize passwordTextField;
#synthesize customersController;
- (void)viewDidLoad {
UIImage *buttonImageNormal = [UIImage imageNamed:#"whiteButton.png"];
UIImage *stretchableButtonImageNormal = [buttonImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0];
UIImage *buttonImagePressed = [UIImage imageNamed:#"blueButton.png"];
UIImage *stretchableButtonImagePressed = [buttonImagePressed stretchableImageWithLeftCapWidth:12 topCapHeight:0];
[loginButton setBackgroundImage:stretchableButtonImageNormal forState:UIControlStateNormal];
[loginButton setBackgroundImage:stretchableButtonImagePressed forState:UIControlStateHighlighted];
}
- (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 {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[usernameTextField release];
[passwordTextField release];
[super dealloc];
}
-(IBAction)textFieldDoneEditing:(id)sender {
[sender resignFirstResponder];
}
-(IBAction)backgroundTap:(id)sender {
[usernameTextField resignFirstResponder];
[passwordTextField resignFirstResponder];
}
-(IBAction)loginToAccount:(id)sender {
// bla bla bla... Login check process is done here
CustomersController *tmpViewController = [[CustomersController alloc] initWithNibName:#"Customers" bundle:nil];
self.customersController = tmpViewController;
[self presentModalViewController:tmpViewController animated:YES];
[self.view removeFromSuperview];
[tmpViewController release];
}
#end
As you can see above, in LoginController.m's loginToAccount method, I am checking the login info and then setting the new view controller for the "customers" sub-view.
Then I am removing the current "Login" subview from the super view but don't know how to add the new "Customers" sub view.
In MainWindow.xib, I have one view controller which is linked to ViewController class and it's the root contoller.
Any help is appreciated. Because I am new to Objective-C and iPhone programming, please do your best to explain considering a novice programmer :)
Thanks again.
Okay, let me answer my question. I just found the answer on StackOverFlow.com
In the view controller which is going to load the next view controller, just add these lines:
NextController *tmpViewController = [[NextController alloc] initWithNibName:#"NextView" bundle:nil];
tmpViewController.enteredUsername = usernameTextField.text;
tmpViewController.enteredPassword = passwordTextField.text;
I'd say that better way is to have separate class for storing globally needed data (and that would be compliant with MVC model).
For example you can store you login information in your MySoftwareAppDelegate, which is easily accessible with [[UIApplication sharedApplication] delegate] call from any part of your application.
It all depends on how serious the data you want to pass it. For a quick variable (maybe a settings change in a modal view controller) TamTam's solution makes the most sense. You alloc/init'ed it, you got the variable, why not access it properties? That same (modally presented) view controller might pass variables back via a delegate pattern.
If you're data needs to be system wide, you can use the singleton pattern. Using "[[UIApplication sharedApplication] delegate]" gets the application delegation (which is a singleton), and many people stuff their variables there for convenience. However, your app delegate wasn't designed for this, and so it's considered bad form. Create your own singleton if your apple isn't a quickie.
If you use a persistent data store like sql, plists or coredata, you can put your system wide data there.
I have an iPhone application that loads succesive views in a framework based on the one explained in this link (basically a main ViewController that loads/removes additional views with a displayView method). In my application I am using NIBs (the example link uses coded views) though so each of my ViewControllers has its accompanying nib.
Debugging in Instruments shows no leaks but if I enter/leave a section (ViewController with its View.xib), the nib remains in memory so after a few in/outs memory starts to accumulate.
I know the nib is not being unloaded because one is almost programmatically created (no stuff in IB) while another does have images and buttons created in IB. The large one is loaded first and the small one loads next. You would expect a reduction in allocation in Instruments.
How can I prevent this?
My structure is as follows, with a few comments below:
`MyAppDelegate.h`
#import <UIKit/UIKit.h>
#class RootViewController;
#interface MyAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
RootViewController *viewController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet RootViewController *viewController;
-(void) displayView:(int)intNewView;
#end
`MyAppDelegate.m`
#import "MyAppDelegate.h"
#import "RootViewController.h"
#implementation MyAppDelegate
#synthesize window;
#synthesize viewController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window addSubview:viewController.view];
[window makeKeyAndVisible];
return YES;
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
}
-(void) displayView:(int)intNewView {
[viewController displayView:intNewView];
}
- (void)dealloc {
[viewController release];
[window release];
[super dealloc];
}
#end
This controller handles subview load/removes:
`RootViewController.h`
#import <UIKit/UIKit.h>
#interface RootViewController : UIViewController {
}
- (void) displayView:(int)intNewView;
#end
`RootViewController.m`
#import "RootViewController.h"
#import "ViewController.h"
#implementation RootViewController
UIViewController *currentView;
- (void) displayView:(int)intNewView {
NSLog(#"%i", intNewView);
[currentView.view removeFromSuperview];
[currentView release];
switch (intNewView) {
case 1:
currentView = [[ViewController alloc] initWithNibName:#"View" bundle:nil];
break;
}
[self.view addSubview:currentView.view];
}
- (void)viewDidLoad {
currentView = [[ViewController alloc]
initWithNibName:#"View" bundle:nil];
[self.view addSubview:currentView.view];
[super viewDidLoad];
}
- (void)dealloc {
[currentView release];
[super dealloc];
}
#end
There would be as many case as "detail" ViewControllers I have (right now I have 3 case but this will grow to 10 or more). The purpose of this structure is to easily move from one "section" of the application to another (NavBar controller or TabBar controller do not suit my specific needs).
`ViewController.h`
// Generic View Controller Example
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
UIImageView *_image1;
UIImageView *_image2;
NSTimer *_theTimer;
}
#property (nonatomic, retain) IBOutlet UIImageView *image1;
#property (nonatomic, retain) IBOutlet UIImageView *image2;
#property (nonatomic, retain) NSTimer *theTimer;
#end
`ViewController.m`
#import "ViewController.h"
#import "MyAppDelegate.h"
#synthesize image1 = _image1, image2 = _image2, theTimer = _theTimer;
- (void)loadMenu {
[self.theTimer invalidate];
self.theTimer = nil;
MyAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate displayView:2];
}
-(void)setView:(UIView*)aView {
if (!aView){
self.image1 = nil;
self.image2 = nil;
}
[super setView:aView];
}
- (void)viewDidLoad {
//some code
[super viewDidLoad];
}
- (void)viewDidUnload {
self.image1 = nil;
self.image2 = nil;
}
- (void)dealloc {
NSLog(#"dealloc called");
[self.theTimer invalidate];
[self.theTimer release];
[self.image1 release];
[self.image2 release];
[super dealloc];
}
Notice the NSLog in dealloc. This is being called (I can see it in the console) but the memory needed for the nib is not freed (Instruments shows an increase in memory allocation when leaving a section, because a new nib is loaded).
Any help will be greatly appreciated. I have tried a million different things and I cannot get the nibs to unload.
After a million different tries I finally ran into this forum.
It states:
Apparently images assigned in IB are loaded into image views using imageNamed. imageNamed caches the images in a way that makes them unloadable. You could load the images in viewDidLoad with initWithContentsOfFile and then assign them to the views.
Somewhere else I had read that imageNamed is the devil so I'd rather not have my images load that way.
(BTW this is iPhone OS 3.1 I'm using)
What I ended up is leaving the UIImageView intact in IB but with an empty .image value. The modified code is something like:
- (void)viewDidLoad {
NSString *path = [NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] resourcePath], #"myImageThatBeforeWasAValueinIB.jpg"];
UIImage *image = [UIImage imageWithContentsOfFile:path];
outlet.image = image;
// do the rest of my stuff as it was
[super viewDidLoad];
}
- (void)dealloc {
outlet.image = nil;
[outlet release], outlet = nil;
[super dealloc];
}
And now everything works like a charm! Memory is recovered when I unload a nib and when I get memory warnings.
So pretty much if you have IBOutlets for UIImageViews and memory is a concern (it always is I guess), you can design all you want in IB and when the time comes to connect them to outlets, remove the image reference in IB and create it from code. IB is really good for laying out your app. It would suck to have to do all that thing by code, but I also found this nice utility that converts nibs to objective c code although I haven't tested it yet.
Did you try setting your outlet variables to nil in dealloc?
You are correctly implementing the setView method, but you are setting your outlet variables to nil in the viewDidUnload method instead of dealloc. As discussed here, you should implement dealloc as follows:
- (void)setView:(UIView *)aView {
if (!aView) { // view is being set to nil
// set outlets to nil, e.g.
self.anOutlet = nil;
}
// Invoke super's implementation last
[super setView:aView];
}
- (void)dealloc {
// release outlets and set outlet variables to nil
[anOutlet release], anOutlet = nil;
[super dealloc];
}
EDIT: if the outlets are UIImageViews, then it may be the case that you need to do
anOutlet.image = nil;
because setting the UIImage’s instance image property should increase the retain count of the UIImage’s instance by 1.