PDA

View Full Version : viewDidUnload - is this correct?




MACloop
Jul 5, 2010, 03:40 AM
Hello,
I have struggled with this before and I hope that I have it correct now? The issue is about viewDidUnload and what variables to release, set to nit etc in this method. As I understand a memorywarning will lead to this method and afterwards the view will be loaded again. So, the variables I have released etc are all allocated or retained or IBOutlets... I have set some comments in the method inorder to show how they are retaiend/allocated.
So, what do you think?

- (void)viewDidUnload {
[self.theStringInfoArray release];//is allocated in the init method, has a property with assign in .h-file
[self.locManager release];/is allocated in the init method, has a property with assign in .h-file
[self.infoPopoverController release];/is assigned in the viewDidLoad method, has a property with retain in .h-file
[startMap release];//is a IBOutlet with a property retain
theLoader = nil;//is a IBOutlet with no property, is not retained or alloced in the class - only used like [theLoader stopAnimating]
}

- (void) dealloc {
[theStringInfoArray release];
[locManager release];
[infoPopoverController release];
[startMap release];
[theLoader release];
[super dealloc];
}


Thanks in adevance!
MACloop



robbieduncan
Jul 5, 2010, 04:14 AM
You should not release any object twice unless you have retained it twice (counting alloc/init as a retain). If you release the object in viewDidUnload what will happen in dealloc?

MACloop
Jul 5, 2010, 04:44 AM
You should not release any object twice unless you have retained it twice (counting alloc/init as a retain). If you release the object in viewDidUnload what will happen in dealloc?

Ok, now I am confused :confused:

I thought that in case of a memorywarning the ViewDidUnload method is called and the dealloc is never called in this case. After the objects/variables are released or set to nil in the viewDidUnload, the view will be loaded again ie viewDidLoad is called again to load the view... If dealloc is called when a memory warning is sended...why does viewDidUnload excist at all...?

MACloop

robbieduncan
Jul 5, 2010, 04:50 AM
viewDidUnload will be called when the view unloads.
dealloc will be called when the view controller itself reaches retain count 0 and is removed from memory. In your application it may be that dealloc is never called, but I would not count on that being the case.

MACloop
Jul 5, 2010, 04:53 AM
viewDidUnload will be called when the view unloads.
dealloc will be called when the view controller itself reaches retain count 0 and is removed from memory. In your application it may be that dealloc is never called, but I would not count on that being the case.

OK, what would you suggest in the case above? Only to set variables being IBOutlets to nil? I mean, if I alloc the variables again in the viewDidLoad method, there will be a leak... Could a solution be to check in the viewdidload if the varaible is nil and if it is - allocate it?

MACloop

robbieduncan
Jul 5, 2010, 04:59 AM
I would set the properties to nil in viewDidUnload, reload them in viewDidLoad and check if they were non-nil and if they were set them to nil in dealloc (in case the view controller somehow reaches retain count 0 without viewDidUnload being called).

I would also note that this:

[self.theStringInfoArray release];

is potentially very unwise. If theStringInfoArray is defined as something like:

@property (retain) NSArray *theStringInfoArray;

then after your release this would print "non-nil":

if (self.theStringInfoArray!=nil)
{
NSLog(@"non-nil");
}

Clearly this can/will lead to crashes. You should not ever leave dangling pointers like this. The correct thing to do is:

self.theStringInfoArray = nil;

This will call the setTheStringInfoArray method which will release the currently held variable and set the pointer to nil which is safe.

MACloop
Jul 5, 2010, 05:54 AM
I would set the properties to nil in viewDidUnload, reload them in viewDidLoad and check if they were non-nil and if they were set them to nil in dealloc (in case the view controller somehow reaches retain count 0 without viewDidUnload being called).

Ok, that makes sence. So if I have a variable with a property assign that is alloc-ed in the viewdidload I check in the viewdidload if it already excists and if not I alloc it. In the viewDidUnload I set this variable to nil. In the dealloc i release this variable or should I set it to nil there aswell?

Variables allocated in the init method? Where should I do what with these? The init is only called when the viewcontroller was removed from the memory?


I would also note that this:

[self.theStringInfoArray release];


is potentially very unwise. If theStringInfoArray is defined as something like:

@property (retain) NSArray *theStringInfoArray;

then after your release this would print "non-nil":

if (self.theStringInfoArray!=nil)
{
NSLog(@"non-nil");
}

Clearly this can/will lead to crashes. You should not ever leave dangling pointers like this. The correct thing to do is:

self.theStringInfoArray = nil;

This will call the setTheStringInfoArray method which will release the currently held variable and set the pointer to nil which is safe.
[/QUOTE]
So in all cases where I have a property and uses setter/getter like this(self.), I should always set this one to nil in the dealloc method? I have read somewhere that using self. in the dealloc in not good practice...?

MACloop
Jul 5, 2010, 06:03 AM
and here another situation:
I have a popovercontroller and it is set with property retain in the .h file. In the viewdidload it is assigned like this:

InfoPopoverViewController* info = [[InfoPopoverViewController alloc] init];
UIPopoverController* infoPopover = [[UIPopoverController alloc] initWithContentViewController:info];
[info release];
infoPopover.delegate = self;

self.infoPopoverController = infoPopover;
[infoPopover release];


After this the self.infoPopoverController has retain +1 right? I am not actually allocating this object in the viewDidLoad - should I set it to nil in the viewDidUnload method? Or only release it in the dealloc?

MACloop

robbieduncan
Jul 5, 2010, 06:49 AM
InfoPopoverViewController* info = [[InfoPopoverViewController alloc] init]; // info retain count is 1
UIPopoverController* infoPopover = [[UIPopoverController alloc] initWithContentViewController:info]; // info retain count is 2
[info release]; // info retain count is 1
infoPopover.delegate = self;

self.infoPopoverController = infoPopover; // info retain count is 2
[infoPopover release]; // info retain count is 1



As to where you should release it: you should release it as soon as you no-longer need it. This could be in any of the methods you mention or potentially earlier.

MACloop
Jul 5, 2010, 06:59 AM
As to where you should release it: you should release it as soon as you no-longer need it. This could be in any of the methods you mention or potentially earlier.

ok, I need it as long as the viewcontroller lives. That would mean I have to release it in the dealloc, right?
MACloop

robbieduncan
Jul 5, 2010, 07:08 AM
ok, I need it as long as the viewcontroller lives. That would mean I have to release it in the dealloc, right?
MACloop

Yes

MACloop
Jul 5, 2010, 07:33 AM
Yes

Ok, at least one part is clear ;-)

I have these situations and I am not sure that I got them all. This is how I would do it:

1)
in .h
IBOutlet NSString *x;
@property(nonatomic, retain)NSString *x;

in .m
self.x = something;

in viewDidUnload
self.x = nil;
dealloc
[x release];

2)
in .h
NSString *x;
@property(nonatomic, retain)NSString *x;

in .m
self.x = something;

viewDidUnload
nothing

dealloc
[x release];

3)
in .h
NSString *x;

in .m
NSString *x = [[NSString alloc]initWithString:@"Something"];

viewDidUnload
nothing

dealloc
[x release];

4)
in .h
NSMutableArray *x;
@property (nonatomic, assign)NSMutableArray *x;

in .m
in the init-method not in viewDidLoad
x = [[NSMutableArray alloc]initWithArray:someArray];

viewDidUnload
nothing

dealloc
[x release];


Is this the right way to do it?
MACloop

robbieduncan
Jul 5, 2010, 07:34 AM
Can you sort out some code tags. The above is unreadable.

Edit, OK I think I've worked out the mess.

1) Think about what self.x=nil is doing. Think about the synthesized setter. What code is in there? Does it do a release? So should you release again?

2) Fine

3) You have shadowed the variable you declared in the .h file in the .m file. This will have unexpected consequences.

4) I don't see what it matter whether the assignment is in init or viewDidLoad. The same memory management rules apply. In this case it should be fine as you release in dealloc.

MACloop
Jul 5, 2010, 07:53 AM
Can you sort out some code tags. The above is unreadable.

Edit, OK I think I've worked out the mess.
sorry....

1) Think about what self.x=nil is doing. Think about the synthesized setter. What code is in there? Does it do a release? So should you release again?
self.x=nil is releasing and removing the variable... I think this is the point - I do not want a leak but I certanly not want a overretain... so should I only release in dealloc? and do nothiong in viewDidUnload?

2) Fine

3) You have shadowed the variable you declared in the .h file in the .m file. This will have unexpected consequences.
Ups is was supposed to be
in .h
NSString *x;

in .m
x = [[NSString alloc]initWithString:@"Something"];

viewDidUnload
nothing

dealloc
[x release];

4) I don't see what it matter whether the assignment is in init or viewDidLoad. The same memory management rules apply. In this case it should be fine as you release in dealloc.Ok, so you would do nothing in viewDidUnload here?

robbieduncan
Jul 5, 2010, 07:58 AM
self.x=nil is releasing and removing the variable... I think this is the point - I do not want a leak but I certanly not want a overretain... so should I only release in dealloc? and do nothiong in viewDidUnload?


As you do not say you are doing alloc/init, just "something" the assumption is that you are calling the setter with an autoreleased variable. The setter will retain it. When you call the setter with nil it will release it. Releasing again in dealloc will be releasing an already fully released object.

Given that you've been asking questions on here about memory management for, quite literally, months it's time to step back and ask why. The rules are very, very simple. Take some time and learn them and then think about what they mean when you apply them. Take a pencil and some paper and actually work through the lifecycles of the objects.

MACloop
Jul 5, 2010, 08:18 AM
As you do not say you are doing alloc/init, just "something" the assumption is that you are calling the setter with an autoreleased variable. The setter will retain it. When you call the setter with nil it will release it. Releasing again in dealloc will be releasing an already fully released object.

Given that you've been asking questions on here about memory management for, quite literally, months it's time to step back and ask why. The rules are very, very simple. Take some time and learn them and then think about what they mean when you apply them. Take a pencil and some paper and actually work through the lifecycles of the objects.

OK, yes I think I have to do that again... it is not that trivial I think 1: because everyone I aske gives me different answers 2: there is no right or wrong - every situation is different... I know what overretain and leaks are and I know that everything I alloc/retain also have to be released sometime later on. What I obviously did get wrong is that both dealloc and viewDidUnload may be called in... and I think the comment in the tempalte from apple is a bit missleading. It says:
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil; but it is not so easy :-(
MACloop

MACloop
Jul 5, 2010, 09:20 AM
So, I read about memory management and about memory warnings once again. Perhaps it is me not being a native english speaker who doea not understand what they are trying to say. I read here (http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmNibObjects.html) and there is noting written about the problematics in using viewDidUnload and dealloc. I also read in the reference for UIView that when the viewDidUnload method is called, the view is nil. That means, as I thought, that everything owned by the view could be set to nil /released. But that is not correct if I understand your former advice correctly? Do I actually have to look in the dealloc if every var is not nil ie:

- (void)viewDidUnload {
if(self.annotation != nil)
self.annotation = nil;
[super viewDidUnload];

}


- (void)dealloc {
if(self.annotation != nil)
[annotation release];
[super dealloc];
}

robbieduncan
Jul 5, 2010, 09:24 AM
I don't use any nibs at all in any of my iPhone apps so hopefully someone who does will come along to help you. I find that creating everything in code means I know exactly when objects will be released so I don't worry about this sort of thing.

MACloop
Jul 5, 2010, 09:30 AM
I don't use any nibs at all in any of my iPhone apps so hopefully someone who does will come along to help you. I find that creating everything in code means I know exactly when objects will be released so I don't worry about this sort of thing.

ok, so you are not doing anything in viewDidUnload?

..and what do you think? Could this be the way to do it? I try not to use nib files but to rewrite the program now would take a very long time. Do you see any problems with the suggestion I did above? Looking in the dealloc and the viewDidUnload if the variable is not nil....

robbieduncan
Jul 5, 2010, 09:30 AM
ok, so you are not doing anything in viewDidUnload?

I'm at work so I don't have access to my code. I might be if I consider it to be the correct thing to do. There is no answer that will be 100% correct all of the time: whether you release an object in viewDidUnload is entirely decided by the code structure of your application: you have to use your judgement to do the correct thing. That's what programming is all about.

..and what do you think? Could this be the way to do it? I try not to use nib files but to rewrite the program now would take a very long time. Do you see any problems with the suggestion I did above? Looking in the dealloc and the viewDidUnload if the variable is not nil....

I don't know and I'm not about to spend time and mental effort reading all the documentation. That's your job!

MACloop
Jul 5, 2010, 09:38 AM
I'm at work so I don't have access to my code. I might be if I consider it to be the correct thing to do. There is no answer that will be 100% correct all of the time: whether you release an object in viewDidUnload is entirely decided by the code structure of your application: you have to use your judgement to do the correct thing. That's what programming is all about.



I don't know and I'm not about to spend time and mental effort reading all the documentation. That's your job!

Yes, that is what programming is about, but I sometime think the documentation is hard to understand and as far as I can see only trivial examples on this is mentioned in the documentation. I know that every situation is different and therefore I made some conrete exapmles before inorder to understand some parts and basics...

MACloop
Jul 5, 2010, 10:11 AM
so one last question:

I have implemented this solution in this specific case:
The reason why the map object is reretained is the blue-dot problem, to avoid the map being released before the blue-dot is done.

.h file
IBOutlet MKMapView *map;
@property(nonatomic,retain) IBOutlet MKMapView *map;

.m file
- (void)viewDidUnload {
if(map != nil){
[self.map retain];//+2
[self.map performSelector:@selector(autorelease) withObject:nil afterDelay:5.0];//-1 after delay
self.map = nil;//-1
}
[super viewDidUnload];
}

- (void)dealloc {
if(map != nil){
[self.map retain];//+2
[self.map performSelector:@selector(autorelease) withObject:nil afterDelay:5.0];//-1 after delay
[map release];//-1
}
[super dealloc];
}

robbieduncan
Jul 5, 2010, 10:18 AM
so one last question:

So what's the question. You have stated what you have done: I don't see a question.

MACloop
Jul 5, 2010, 10:30 AM
So what's the question. You have stated what you have done: I don't see a question.

well, the question is if this is a safe way to do this?

robbieduncan
Jul 5, 2010, 10:32 AM
well, the question is if this is a safe way to do this?

In all honesty using performSelector:withObject:afterDelay: to call autorelease looks like it almost has to be an error.

MACloop
Jul 5, 2010, 12:11 PM
In all honesty using performSelector:withObject:afterDelay: to call autorelease looks like it almost has to be an error.

ok, why? I have not done it my self but got the advice from other people having problem with the mapkit. The problem is that dealloc in the viewcontreoller may be called before the map is done loading and that will lead to a crash. So you have to delay the release some how. It seem to work though in my app and I have used it in a navigationcontroller app aswell without any problems. What would you suggest as alternative? And why is it an error?

MACloop

bredell
Jul 5, 2010, 12:19 PM
Hello,
I have struggled with this before and I hope that I have it correct now? The issue is about viewDidUnload and what variables to release, set to nit etc in this method. As I understand a memorywarning will lead to this method and afterwards the view will be loaded again. So, the variables I have released etc are all allocated or retained or IBOutlets... I have set some comments in the method inorder to show how they are retaiend/allocated.
So, what do you think?

The documentation has a section that tells you where to initialize and release your memory objects: Managing Memory Efficiently (http://developer.apple.com/iphone/library/featuredarticles/ViewControllerPGforiPhoneOS/BasicViewControllers/BasicViewControllers.html#//apple_ref/doc/uid/TP40007457-CH101-SW4).

You are correct that a low memory situation will trigger the viewDidUnload method and you're supposed to release as much memory as possible in that method, but it should only be objects that you can later recreate in the viewDidLoad method.

MACloop
Jul 5, 2010, 12:39 PM
The documentation has a section that tells you where to initialize and release your memory objects: Managing Memory Efficiently (http://developer.apple.com/iphone/library/featuredarticles/ViewControllerPGforiPhoneOS/BasicViewControllers/BasicViewControllers.html#//apple_ref/doc/uid/TP40007457-CH101-SW4).

You are correct that a low memory situation will trigger the viewDidUnload method and you're supposed to release as much memory as possible in that method, but it should only be objects that you can later recreate in the viewDidLoad method.

Thanks for the comment! Yes I know about this - what suprised me was the problem with dealloc ie that you cannot release any objects in the viewDidUnload method without the risk to have them overreleased later on. I though that the viewController did not call dealloc when the viewDidUnload was called... This means that I have to look if the variables are exsisting before I kill them...

robbieduncan
Jul 5, 2010, 12:45 PM
ok, why? I have not done it my self but got the advice from other people having problem with the mapkit. The problem is that dealloc in the viewcontreoller may be called before the map is done loading and that will lead to a crash. So you have to delay the release some how. It seem to work though in my app and I have used it in a navigationcontroller app aswell without any problems. What would you suggest as alternative? And why is it an error?

MACloop

I have not used MapKit for anything, perhaps this is a possible solution for that. As I said it just looks like it has to be wrong: it looks like an inelegant hack around a problem instead of tackling it "correctly". If it works right now that's great but if your app suddenly starts crashing as the delay is 0.1 seconds too short how will you know? The crash could happen in any semi-random looking place and be difficult to track back to here.

If you need to delay releasing that object until the map has finished loading then you should (I think) hold your deallocation until this delegate method (http://developer.apple.com/iphone/library/documentation/MapKit/Reference/MKMapViewDelegate_Protocol/MKMapViewDelegate/MKMapViewDelegate.html#//apple_ref/occ/intfm/MKMapViewDelegate/mapViewDidFinishLoadingMap:) fires then dealloc. That way you know 100% for sure it's happening at the correct time.

MACloop
Jul 5, 2010, 12:49 PM
I have not used MapKit for anything, perhaps this is a possible solution for that. As I said it just looks like it has to be wrong: it looks like an inelegant hack around a problem instead of tackling it "correctly". If it works right now that's great but if your app suddenly starts crashing as the delay is 0.1 seconds too short how will you know? The crash could happen in any semi-random looking place and be difficult to track back to here.

If you need to delay releasing that object until the map has finished loading then you should (I think) hold your deallocation until this delegate method (http://developer.apple.com/iphone/library/documentation/MapKit/Reference/MKMapViewDelegate_Protocol/MKMapViewDelegate/MKMapViewDelegate.html#//apple_ref/occ/intfm/MKMapViewDelegate/mapViewDidFinishLoadingMap:) fires then dealloc. That way you know 100% for sure it's happening at the correct time.

Ok, thanks! Because this is a "well-known-apple-bug" I hope it will be dissapearing in some update in the near future. But I give you right on that - it is not 100% safe. Anyway, the rest is ok? Is it ok to look if a variable is not nil in both viewDidUnload and in dealloc?
MACloop

EDIT: ups that delegate method is new I think.... I have to try it out!

robbieduncan
Jul 5, 2010, 12:53 PM
Ok, thanks! Because this is a "well-known-apple-bug" I hope it will be dissapearing in some update in the near future. But I give you right on that - it is not 100% safe. Anyway, the rest is ok? Is it ok to look if a variable is not nil in both viewDidUnload and in dealloc?
MACloop

EDIT: ups that delegate method is new I think.... I have to try it out!

Yes, I think it's OK to potentially dealloc in both places. I suspect that it should not be possible to reach the view controller dealloc before viewDidUnload runs I think it's prudent to allow for that.

The delegate method claims to have been there since 3.0 but as I've never used MapKit I can only go by the documentation!

MACloop
Jul 5, 2010, 01:10 PM
Yes, I think it's OK to potentially dealloc in both places. I suspect that it should not be possible to reach the view controller dealloc before viewDidUnload runs I think it's prudent to allow for that.

The delegate method claims to have been there since 3.0 but as I've never used MapKit I can only go by the documentation!

Ok, then I will go for the approach with checking if a variable is nil or not.

hmm... yes the method is not new... I have to investigate it but I suppose it does not solve my problem, because it is a bug in the framework. I thought the newest update had come with some new goodies on this issue....
Thanks for the hints!
MACloop

Luke Redpath
Jul 5, 2010, 07:33 PM
Let me try and clear is up for you.

On top of the normal memory management rules, the following rules apply:

1. If your IBOutlets are properties with the 'retain' keyword, then it is down to you to ensure they are released when the view is unloaded. That is because your subviews are being retained twice. Once, by its parent view and once by your controller. When the parent view is unloaded, it will relinquish it's ownership of the subviews but its down to you to make sure your controller does too, in viewDidUnload.

2. The advised way of doing this is to both release and nullify the pointer otherwise you may leave dangling pointers to deallocced objects. You can do this with one line but be aware of any side effects if you have overridden any of your property setter methods.


- (void)viewDidUnload
{
[super viewDidUnload]

self.somesubview = nil;
}


3. You should also release your instance variables directly in dealloc as usual. There is no need to check if they are nil, just call release. It is safe to call release on nil (that is why you set your properties to nil above instead of just releasing them).

Hope that helps.

A small aside, Im still baffled by people who resist Interface Builder and there is nothing worse than inheriting a codebase that doesn't use nibs. Writing all of your view initialisation, configuration and layout in code buys you little to nothing and results in a codebase that is harder to maintain. Embrace IB. Apple recommend you use it for good reason and Im happy to see it even more tightly integrated with Xcode in v4. There are times when manually writing view code makes sense but 80% of the time you should just let IB take care of the tedious boilerplate for you.

If your gut is telling you to avoid Interface Buidler, for whatever reason, your gut is wrong. This isn't one of those cases where there's more than one right way and it's purely a matter of personal preference. Interface Builder is clearly the preferred way and with very good reason: it makes your applications faster to create and easier to maintain. —Jeff LaMarche
http://iphonedevelopment.blogspot.com/2009/10/dont-fear-interface-builder.html

This isn't really a dig at robbieduncan because he isn't the only person I've come across who tries to resist using Nibs but honestly, all you are doing is creating more work for yourself and costing your clients time and money, and obfuscating the interesting code with unnecessary boilerplate.

MACloop
Jul 6, 2010, 03:04 AM
So I have implemeted as I wrote yesterday and it seem to work out. One more question:
When a memory warning is sended, the actual view is not changed, but the other views (I use a tabbar) are reseted and load again. Why is that?
MACloop

MACloop
Jul 7, 2010, 09:48 AM
Let me try and clear is up for you.

On top of the normal memory management rules, the following rules apply:

1. If your IBOutlets are properties with the 'retain' keyword, then it is down to you to ensure they are released when the view is unloaded. That is because your subviews are being retained twice. Once, by its parent view and once by your controller. When the parent view is unloaded, it will relinquish it's ownership of the subviews but its down to you to make sure your controller does too, in viewDidUnload.

2. The advised way of doing this is to both release and nullify the pointer otherwise you may leave dangling pointers to deallocced objects. You can do this with one line but be aware of any side effects if you have overridden any of your property setter methods.


- (void)viewDidUnload
{
[super viewDidUnload]

self.somesubview = nil;
}


3. You should also release your instance variables directly in dealloc as usual. There is no need to check if they are nil, just call release. It is safe to call release on nil (that is why you set your properties to nil above instead of just releasing them).

Hope that helps.

A small aside, Im still baffled by people who resist Interface Builder and there is nothing worse than inheriting a codebase that doesn't use nibs. Writing all of your view initialisation, configuration and layout in code buys you little to nothing and results in a codebase that is harder to maintain. Embrace IB. Apple recommend you use it for good reason and Im happy to see it even more tightly integrated with Xcode in v4. There are times when manually writing view code makes sense but 80% of the time you should just let IB take care of the tedious boilerplate for you.


http://iphonedevelopment.blogspot.com/2009/10/dont-fear-interface-builder.html

This isn't really a dig at robbieduncan because he isn't the only person I've come across who tries to resist using Nibs but honestly, all you are doing is creating more work for yourself and costing your clients time and money, and obfuscating the interesting code with unnecessary boilerplate.

Wow! Thanks alot! Your answer is perfect! I am sorry I did not give you feedback earlier - I just saw it! Very nice explanation!
MACloop