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.
Related
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.
I have too much code to know which i need to quote here, but in my app delegate I have an NSMutableArray. Then in another class, it creates a new entry to the NSMutableArray but upon passing back to another class which should use that to display something on screen, it doesn't display anything. Putting an NSLog for the NSMutableArray count at the end of the class creating it displays the number 1, and then putting the same NSLog code at the start of the class which is meant to use that returns 0.
Any ideas why this is?
EDIT: Ok, i'll try and include all related code..
app delegate.h:
#interface palettesAppDelegate : NSObject <UIApplicationDelegate> {
NSMutableArray *colourPalettesContainer;
}
#property (assign, readwrite) NSMutableArray *colourPalettesContainer;
#end
app delegate.m:
#import "palettesAppDelegate.h"
#implementation palettesAppDelegate
#synthesize colourPalettesContainer;
- (void)dealloc {
[colourPalettesContainer release];
[super dealloc];
}
#end
Homeview.h:
#import <UIKit/UIKit.h>
#import "HandlingPalettes.h"
#interface HomeView : UIViewController {
HandlingPalettes *handlingPalettes;
}
#end
Homeview.m:
#import "HomeView.h"
#import <QuartzCore/QuartzCore.h>
#implementation HomeView
- (void)viewDidLoad {
[super viewDidLoad];
handlingPalettes = [[HandlingPalettes alloc] init];
[handlingPalettes newPalette];
}
-(void)viewWillAppear:(BOOL)animated {
NSLog(#"view will appear: %i", [dataCenter.colourPalettesContainer count]);
int numberOfExisting = [dataCenter.colourPalettesContainer count];
}
- (void)dealloc {
[handlingPalettes release];
[super dealloc];
}
#end
HandlingPalettes.h:
#import <UIKit/UIKit.h>
#interface HandlingPalettes : UIViewController {
}
-(void)newPalette;
#end
HandlingPalettes.m:
#import "HandlingPalettes.h"
#import "HomeView.h"
#import "palettesAppDelegate.h"
#implementation HandlingPalettes
-(void)newPalette {
palettesAppDelegate *dataCenter = (palettesAppDelegate *)[[UIApplication sharedApplication] delegate];
//If this is the first palette
if (dataCenter.colourPalettesContainer == nil) {
dataCenter.colourPalettesContainer = [[NSMutableArray alloc] init];
}
//Add a new palette
[dataCenter.colourPalettesContainer addObject:#"Test1", #"Test2", nil];
NSLog(#"Handling: %i", [dataCenter.colourPalettesContainer count]);
}- (void)dealloc {
[super dealloc];
}
#end
Your main mutablearray is in your app delegate. So, see what happens if in EVERY METHOD that you want to access the array you have the line to set up the app delegate relationship
palettesAppDelegate *dataCenter = (palettesAppDelegate *)[[UIApplication sharedApplication] delegate];
Now, when you call the dataCenter object you will be referencing the App Delegate and your program will find the array.
You may also find that you will need to have an #import "palettesAppDelegate.h" in each object that is going to reference the App Delegate.
Note, just adding the app delegate code is not necessarily the proper way to deal with this issue from an architectural standpoint. But if it works you at least know the answer to your original question.
I suspect the problem is ultimately related to confused memory management of the colourPalettesContainer member. You release it in the app delegate's dealloc method, but that class never retains it! It would be much cleaner if you'd follow Apple's memory management guidelines: your classes should only release objects that they own (i.e., that they themselves retained earlier). For example, you can do this by declaring the array's property retain:
#property (retain) NSMutableArray *colourPalettesContainer;
(To prevent leaking the array, you'll also need to release or autorelease it in the newPalette method. Retain and release should always come in close pairs.)
But even better, why not simply create the array in the app delegate's init method, or in its accessor (if for some reason you want to continue creating it only on its first use)? Unless you want to replace all palettes at once, there is no reason to let the array be assigned to from outside the app delegate.
#interface PalettesAppDelegate : NSObject <UIApplicationDelegate> {
#private
NSMutableArray *colourPalettesContainer;
}
#property (readonly) NSMutableArray *colourPalettesContainer;
#end
#implementation PalettesAppDelegate
- (NSMutableArray *)colourPalettesContainer {
if (colourPalettesContainer == nil) {
colourPalettesContainer = [[NSMutableArray alloc] init];
return colourPalettesContainer;
}
- (void)dealloc {
[colourPalettesContainer release];
[super dealloc];
}
#end
To make the design even cleaner, change the type of the colourPalettesContainer property to NSArray * and add an -addPalette: method to the app delegate. (It is rarely a good idea to publicly expose a mutable array inside a class.) You can then simply get rid of -newPalette in HandlingPalettes. (If you want to have all your palette-handling methods in HandlingPalettes, then simply move the array there. If you need to access the palettes from random places in your app, then you can simply put a retained reference to your HandlingPalettes object in the app delegate.)
Once you clean up the object ownership mess, the count mismatch will either resolve itself "by magic" or the cause will likely become much more obvious. In the latter case, check that the HomeView's dataCenter is actually the same object as the one in HandlingPalettes. (You omitted how HomeView gets its reference — are you sure you aren't creating another instance of the app delegate by accident?)
(By the way, you probably meant to use -addObjects:, not -addObject: in newPalette. Note also that all class names should be capitalized, with no exceptions: i.e., always use PalettesAppDelegate, never palettesAppDelegate. If for some reason Xcode's project template created it like that, simply rename the class. Lowercase class names are much too easy to confuse with variable names. Also, try to find better names in general: e.g., instead of HandlingPalettes, I'd use PalettesViewController (to reflect the fact that it is a subclass of UIViewController); and instead of dataCenter, I'd rather just choose appDelegate.)
I would be inclined to get rid of the newPalette method, and instead create a getter method for colourPalettesContainer in your app delegate.
ie:
appdelegate.h
#interface PalettesAppDelegate : NSObject <UIApplicationDelegate> {
NSMutableArray *colourPalettesContainer;
}
#property (non-atomic, retain) NSMutableArray *colourPalettesContainer;
#end
#implementation palettesAppDelegate
appdelegate.m
#import "appdelegate.h"
#synthesize colourPalettesContainer;
- (NSMutableArray *) colourPalettesContainer{
if(colourPalettesContainer==nil){
colourPalettesContainer=[[NSMutableArray alloc] init];
}
return colourPalettesContainer;
}
- (void)dealloc {
[colourPalettesContainer release];
[super dealloc];
}
#end
you should then be able to add items by calling
[appDelegate.colourPalettesContainer addObject:object];
Suppose I have two classes. In the first one I declare this in Class1.h
#interface Class1 : UIViewController {
NSString *myString;
id myObject;
}
On the second class I go beyond that I declare it like
#interface Class2 : UIViewController {
NSString *myString;
id myObject;
}
#property (nonatomic, retain) NSString *myString;
#property (nonatomic, retain) id myObject;
and then I #synthesize myString, myObject on Class2.m
Then, on my main program, I create two objects: one based on Class1 and another one based on Class2.
What effect the #property of class2 will have? Will every value assigned to both values on Class2 be always retained? If so, do I need to "release" them? How?
Thanks.
Please read Declared Properties section of The Objective-C programming language
for a full explanation on properties ;)
In Class2:
In this case you set retain attribute to your property it is supposed to be retained in the implementation. This is done automatically when you synthesize a property.
This means that you should have
- (void) dealloc{
[myString release];
[myObject release];
[super dealloc];
}
and everything should be fine
In Class1, you don't have properties so myString and myObject is not visible from outside. But this does not mean that you shouldn't release them. It depends on the way you initialize them and/or if you send retain messages to them.
BTW, if you set assign a property you don't release it, just set it to nil in the dealloc method. If you set copy to it then you must release it.
EDIT
You said: *But suppose I have this *
#property (nonatomic, retain) UIView *myView;
and
myView = [[UIView alloc] initWithFrame:myFrame];
[self.view addSubview:myView];
[myView release];
? I am already releasing myView... do I have to release it again???
First, since you have your property defined that way, you should have dealloc method as:
- (void) dealloc{
[myView release];
[super dealloc];
}
So, the answer is NO you should not release it but actually is not correct.
Please take a look:
myView = [[UIView alloc] initWithFrame:myFrame]; //myView retainCount is 1
[self.view addSubview:myView]; //retainCount is 2
[myView release]; //retainCount is 1 again
later in dealloc method
- (void) dealloc{
[myView release]; // retainCount becomes 0, is deallocated
[super dealloc]; // subviews of self.view are released but myView was already deallocated!, so you have over released myView once ;(
}
This is the correct way: (Use your properties ;) )
UIView *aView = [[UIView alloc] initWithFrame:myFrame]; // init, retainCount is 1
self.myView = aView; // retainCount becomes 2
[aView release]; // retainCount becomes 1 again and we are fine.
[self.view addSubview:self.myView]; //retainCounts becomes 2 again.
even if it is 2 there is no problem because when self.view is deallocated its subviews also will be released. Hence self.myView retainCount will become 1 again later when self is deallocated.
- (void) dealloc{
[myView release]; //retainCounts becomes 1
[super dealloc]; // all its subviews of self.view are released hence myView retaincount becomes 1 and is released corretly
}
What is the difference?
Suppose self.myView is also retained by other object X and with the former approach, X's view will be pointing to an invalid object, because it was already released.
Hope it helps
EDIT2
As bbum's indication, this is a mini-mini-short tutorial on properties:
when you have
#property (... retain) NSObject *retainVar;
#property (... assign) NSObject *assignVar;
#property (... copy) NSObject *copyVar;
and you #synthesize them
is like having the following setters:
// retain
-(void)setRetainVar:(NSObject *)var {
if (retainVar != var) {
[retainVar release];
retainVar = [var retain];
}
}
//assign
-(void)setAssignVar:(NSObject *)var {
assignVar = var;
}
//copy
-(void)setCopyVar:(NSObject *)var {
if (copyVar != var) {
[copyVar release];
copyVar = [var copy];
}
}
(this means that if you assign directly an object you have to make sure is something equivalent to above setters, from the memory management point of view)
and your dealloc method should be something like:
- (void) dealloc{
[retainVar release];
assignVar = nil;
[copyVar release];
[super dealloc];
}
When setting your ivars
for example, inside of init:
- (id) init{
if ((self = [super init])){
//this is ok
retainVar = [[NSObject alloc] init];//but is retainVar was not nil we will have a leak ;(
//This is better
NSObject *obj = [NSObject alloc] init];
self.retainVar = obj;
[obj release];
//this is BAD
assignVar = [[NSObject alloc] init];//because this is like retaining it, later it will leak
//below is correct
NSObject *obj = [[[NSObject alloc] init] autorelease];
assignVar = obj;
//copy is pretty much like retain,
//this is ok
copyVar = [[NSObject alloc] init]; //but, if copyVar was not nil is a leak!
//below is better
NSObject *obj = [NSObject alloc] init]:
self.retainVar = obj;
[obj release];
}
return self;
}
Apple's "Learning Objective C - A Primer" tells you about that and more:
http://developer.apple.com/library/ios/#referencelibrary/GettingStarted/Learning_Objective-C_A_Primer/
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 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.