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.
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 began from a tutorial example that had a UIViewController connected to a nib, but now i've decided to do it all programmatically. Consequently, I deleted the nib but without knowing how to implement my controller.
I did something like this:
EventsDetailController *myChild = [[EventsDetailController alloc] init];
[self.navigationController pushViewController:myChild animated:YES];
However, it crashes when I click the specific cell.
Do I have to initWith something? Before when I had a nib it was initWithNib
#import <UIKit/UIKit.h>
#interface EventsDetailController : UIViewController {
NSString *message;
}
#property (nonatomic, copy) NSString *message;
#end
#import "EventsDetailController.h"
#implementation EventsDetailController
#synthesize message;
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
}
-(void)viewDidLoad{
UILabel *theMsg = [[UILabel alloc] initWithFrame:CGRectMake(0,0,200,30)];
theMsg.text = #"hello";
[theMsg release];
[super viewDidLoad];
}
-(void)viewDidUnload{
self.message = nil;
[super viewDidUnload];
}
-(void)dealloc{
[message release];
[super dealloc];
}
#end
Perhaps, I'll start from scratch, I think it is still looking for the nib I deleted.
I'm assuming that you have the app delegate's window.rootViewController set to a NavigationController instance, and that instance was created with a initWithRootViewController, as you mentioned that you're getting to the point where it crashes when you click on a cell.
Looking at the added code, the only thing I see that's strange is in the 'veiwDidLoad' method - you alloc and release 'theMsg', but don't use it. I'm assuming you've cut out some code for brevity.
I ended up starting from scratch when I went away from .nib files (about 5 minutes after starting my first iPhone app). I would run with that, adding in your functionality a little at a time.
Why does my app crash when i try to release the image I created with +imageNamed: inside dealloc. My code is as follows:
MyClass.h:
#import <UIKit/UIKit.h>
#interface MyClass{
UIImage *_thumbImage;
}
#property (nonatomic,retain) UIImage *thumbImage;
#end
MyClass.m:
#import "MyClass.h"
#implementation MyClass
#synthesize thumbImage = _thumbImage;
-(void)viewDidLoad{
[super viewDidLoad];
self.thumbImage = [UIImage imagedNamed:#"myImage.png""];
}
-(void)viewDidUnload{
self.thumbImage = nil;
}
-(void)dealloc{
[super dealloc];
[_thumbImage release]; //if i leave this here, the app crashes. should i release my property?
}
#end
In your dealloc method, you need to move [super dealloc] to the bottom. You're trying to access your object's instance variable after it's been dealloced.
[UIImage imagedNamed:#"myImage.png""];
is autoreleased. It is also memory managed for you. If you need to release it immediately then alloc/init a UIImage or create UIImageView.
#import <UIKit/UIKit.h>
#interface MyClass{
}
#end
#import "MyClass.h"
#implementation MyClass
-(void)viewDidLoad{
[super viewDidLoad];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRect(0,0,100,200)];
imageView.image = [UIImage imagedNamed:#"myImage.png""];
[self.view addSubView:imageView];
[imageView release];
}
-(void)viewDidUnload{
}
-(void)dealloc{
[super dealloc];
}
You need to release your stuff first, then call [super dealloc]. [super dealloc] will release the classes memory and accessing an ivar afterwards will result in an segfault.
You might want to review apples memory management guide here:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html
Particularly the section stating:
You take ownership of an object if you create it using a method whose name begins with “alloc”, “new”, “copy”, or “mutableCopy” (for example, alloc, newObject, or mutableCopy), or if you send it a retain message.
Since you are not using one of these functions you do not have ownership of the object and therefore can not release it.
So, I'm loading by XIB file and it contains a set of UIBarButtonItems. Some of the items are used when the viewDidLoad: is called.
#interface MyViewController : UIViewController {
IBOutlet UIBarButtonItem *addButton;
IBOutlet UIBarButtonItem *editButton;
IBOutlet UIBarButtonItem *doneButton;
}
// NB: There are no properties retaining anything.
#end
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *initialToolbarItems =
[[NSArray alloc] initWithObjects: addButton, editButton, nil];
self.toolbarItems = initialToolbarItems;
[initialToolbarItems release];
}
- (void)dealloc {
[super dealloc];
// Nothing else to do here since we are not retaining anything.
// … or are we? <insert dramatic music here>
}
#end
If I push the above the above ViewController onto a UINavigationController everything seems fine, all the IBOutlets are assigned and behave like expected.
The instant i pop the ViewController from the navigation stack Instruments' Leaks tells me that I am leaking a UIBarButtonItem. Woe is me!
If I change dealloc: to
- (void)dealloc {
[doneButton release];
[super dealloc];
}
no leaks occur. The same goes if I use doneButton in viewDidLoad:
NSArray *initialToolbarItems =
[[NSArray alloc] initWithObjects: addButton, editButton, doneButton, nil];
My question: Why is my IBOutlet leaking when I don't use it. I don't retain it at any point. The the NIB loader should own the object, right?
Only thing I can think of:
The nib loader treats IBOutlets as strong references. All outlets are retained by default unless you specifically indicate assign. So you still need to release them in dealloc and viewDidUnload.
You can also use a assigned property to make it a weak reference:
#property (nonatomic, assign) IBOutlet UIBarButtonItem *doneButton;
Some reading: http://weblog.bignerdranch.com/?p=95
If you have #property with (retain) declared for the your IBOOutlets they will be retained and must be released
The array retains them
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.