views:

2485

answers:

2

Hello

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.

A: 

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.

unforgiven
Thanks for your reply. How would this work with "_name" ivars and "name" properties? [self.anOutlet release], self.anOutlet = nil stops with an "message sent to deallocated instance" error and [self.anOutlet release], _anOutlet = nil has no effect (allocation-wise)
mga
...or should I just not use _name ivars and name properties?
mga
[_name release], _name = nil; does not break but no allocation change either
mga
I did a NSLog(@"RC: %i", [anOutlet retainCount]); in the dealloc and it is throwing 2. Who else could be retaining this guy? The only occasions where I use them are of the type [self.outlet setAlpha:0.0]; In that case the outlet is a UIImageView. In IB the outlet is being connected to the corresponding UIImageView and nothing else.
mga
Thanks for the edit. The thing is: the images are inserted in IB and not programatically. The outlet is just a reference to the image so I can animate it. In dealloc I am now doing: self.outlet.image = nil; [self.outlet release]; self.outlet = nil; (that leads to a crash as in my first comment) and _outlet.image = nil; [_outlet release]; _outlet = nil; does not crash but does not unload any memory.
mga
+4  A: 

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.

mga
You can download my version of the framework with the fix here: http://www.mauriciogiraldo.com/blog/2009/10/09/multiples-views-no-jerarquicas-en-iphone/Scroll down for the english translation.
mga