Why release a property that you've already set to nil? - objective-c

Here are two methods in a view controller from an Apple tutorial:
- (void)viewDidUnload {
self.eventsArray = nil;
self.locationManager = nil;
self.addButton = nil;
}
- (void)dealloc {
[managedObjectContext release];
[eventsArray release];
[locationManager release];
[addButton release];
[super dealloc];
}
Couldn't the dealloc method be shortened to the following? If not, why not?
- (void)dealloc {
[managedObjectContext release];
[super dealloc];
}

- (void)viewDidUnload is not guaranteed to be called, so you should always release things in dealloc too.
See this question to find out when it's called, and what you should do when it is.

No, because you cannot rely on viewDidUnload being called upon deallocation. viewDidUnload is only called when the view controller receives a memory warning while its view is not on screen. If the view controller gets deallocated, viewDidUnload is not called (AFAIK, I'm not entirely sure).

because it's a good practice to always cleanup your ivars in dealloc. something could go wrong, or you may encounter an execution you do not expect.

Setting eventsArray to nil just means it has no content, but still space for content
Calling [eventsArray release] releases the space the array consumed.

Related

Not calling [super dealloc] inside a static UIViewController subclass

In my app I have a UIViewController subclass (VC for short) that I only use in one place in the entire app. In that place, I have been creating and pushing it like this:
MyViewController* VC = [MyViewController new];
[self.navigationController pushViewController:VC animated:YES];
[VC release];
but I was thinking that since this is the only place I am using a view controller of this type, I could do something like this so the settings used won't be reset each time the view controller is pushed onto the stack:
static MapsToSendPicker* VC = nil;
if(!VC) {
VC = [MapsToSendPicker new];
}
[self.navigationController pushViewController:VC animated:YES];
[VC release];
The problem with that code is that in VC's dealloc method, I release all of my instance variables and set them to nil, and finally I call [super dealloc]. This deallocates the static view controller, but the test if(!VC) isn't evaluated to true after (this would defeat the purpose of the whole idea if it were; then I'd have to recreate the view controller each time anyway).
My solution is overriding the dealloc method in MyViewController and not calling [super dealloc] at the end. This works, but the compiler raises a warning. How can I get rid of that warning while maintaining the functionality I gain with this design? Thanks!
Edit:
After a quick Google search, I have found this solution:
- (void)dealloc {
if(NO) {
[super dealloc];
}
}
but I would like something a little bit... cleaner. Any thoughts?
Remove the [VC release]; line and add [super dealloc] back. Everything will work properly and dealloc will never get called. Generally you should consider using NSUserDefaults in order to restore the VC properties instead of keeping the controller in memory all the time.

Objective C - iOS - Dealloc is being called in middle of execution of webViewDidFinishLoad

I am having an issue with memory management in ios. The problem is when I push a viewController which has a webView on to the navigation stack and when I click back before the webview is loaded I am getting exec_bad_access.
In 'Class A' I am creating a NewViewController, then I am pushing it on to the navigation stack, and then releasing it. So here I am giving away my ownership as I am releasing it.
Class A:
-(void)onButtonClick{
NewViewController* viewController = [[NewViewController alloc] init];
[self.navigationController pushViewController: viewController........];
[viewController release];
}
Class B has a webView and a timer in it and implements UIWebViewDelegate. So, in here when the webView shouldStartLoad I am starting the timer. And then when it is done loading I am invalidating it.
Class B:
#interface NewViewController : UIViewController <UIWebViewDelegate>
NSTimer* timer
......
#property(nonatomic, retain) IBOutlet UIWebView* webView;
#end
#implementation
-(void)viewDidLoad{
[super viewDidLoad];
[webView loadRequest:someRequest];
}
.....
.....
-(void)dealloc{
[self makeTimerNil];
[self.webView stoploading];
self.webView.delegate = nil;
[self.webView release];
self.webView = nil;
.....
[super dealloc];
}
-(void)resetTimer{
[self makeTimerNil];
//timer will retain target - self
timer = [NSTimer scheduledTimerWithTimeInterval:kNetworkTimeOut target:self selector:#selector(networkTimedOut) userInfo:nil repeats:NO];
}
-(void)makeTimerNil{
if([timer isValid]){
[timer invalidate];
timer = nil;
}
}
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
[self resetTimer];
......
return YES;
}
-(void)webViewDidFinishLoad:(UIWebView *)webView{
//NO Exception. Can access self
[self anotherMethod];
//timer releases retained target -self
[self makeTimerNil];
//Exception self has been deallocated
[self anotherMethod];
}
#end
But the issue is when the webView is loading if I click back button on the navigation bar, the newViewController is getting deallocated which is fine. But this is happening in the middle of execution of webViewDidFinishLoad. Why is dealloc being called in the middle of execution of webViewDidFinishLoad? Don't they run on the same thread (Main - UI Thread) ?
Any ideas on how to fix the issue?
Your problem is absolutely the timer. According to the NSTimer documentation, an active timer holds a retain on its target object. As a result, your controller cannot get dealloc'd while the timer is active. That in itself is a bug in your architecture, since from your -dealloc method it's obvious you're expecting the view controller to be dealloc'd while the timer is active. But in the case of the webview, it's causing another problem. Specifically, in the middle of your -webViewDidFinishLoad: method you're canceling your timer. This causes it to release its target, and since it was the only owner of your view controller, the view controller immediately deallocs.
dealloc will be called when the retain count drops to 0 and will happen immediately, not later on. It is happening on the thread that the count drops to 0 in, in your case the thread that webViewDidFinishLoad: is called in.
One this you could do here is to add a [self retain] at the top of your webViewDidFinishLoad: method and a [self release] at the bottom of it. However I'm not 100% sure if that method is guaranteed to run on the main thread and if a view controller gets dealloc'ed on a thread other than the main thread then there can be lots of problems.
To fix this really, I would override viewDidDisappear: and do all the tearing down of the web view there (i.e. setting the delegate to nil and stopping the timer, etc). That way it'll happen on the main thread and at the point you tap the back button.
You are correctly nil'ing out the web view's delegate in -dealloc.
I think (but this might be wrong) that, as UI delegate methods, the web view delegate methods will always come in on the main thread, so that shouldn't be an issue. Nowhere else would threading come into play here.
Something that strikes me as a possible problem, though, is unrelated to your web view code per se.
Consider the interaction between
#property(nonatomic, retain) IBOutlet UIWebView* webView;
and
[self.webView release];
self.webView = nil;
Since you have declared webView as retained, won't self.webView = nil send a release to webView (and retain to nil, which of course doesn't do anything)? Isn't this then an over-release of webView?
(As a general rule, you shouldn't use accessors in init/dealloc, in part due to side-effects.)
EDIT:
To examine this further, I wrote a quick test using the view-based app template. Relevant portions below:
// In MyClass.m
+ (id)alloc
{
NSLog(#"alloc");
return [super alloc];
}
- (void)dealloc
{
NSLog(#"dealloc");
[super dealloc];
}
- (oneway void)release
{
NSLog(#"release");
[super release];
}
- (id)retain
{
NSLog(#"retain");
return [super retain];
}
// In UIViewController subclass
// .h:
#import "MyClass.h"
#property (retain, nonatomic) MyClass *myObj;
// .m:
#synthesize myObj = _myObj;
- (void)viewDidLoad
{
[super viewDidLoad];
[self setMyObj:[[[MyClass alloc] init] autorelease]];
[self setMyObj:nil];
}
This produced the following output, indicating that setting nil does, as I expected, release the old object:
2012-01-30 11:04:37.277 ReleaseTest[56305:f803] alloc
2012-01-30 11:04:37.278 ReleaseTest[56305:f803] retain
2012-01-30 11:04:37.279 ReleaseTest[56305:f803] release
2012-01-30 11:04:37.282 ReleaseTest[56305:f803] release
2012-01-30 11:04:37.283 ReleaseTest[56305:f803] dealloc
An, unsurprisingly, adding an extraneous release crashed the app with an EXC_BAD_ACCESS:
- (void)viewDidLoad
{
[super viewDidLoad];
[self setMyObj:[[[MyClass alloc] init] autorelease]];
[[self myObj] release];
[self setMyObj:nil];
}
2012-01-30 11:06:22.815 ReleaseTest[56330:f803] alloc
2012-01-30 11:06:22.817 ReleaseTest[56330:f803] retain
2012-01-30 11:06:22.818 ReleaseTest[56330:f803] release
2012-01-30 11:06:22.818 ReleaseTest[56330:f803] release
2012-01-30 11:06:22.819 ReleaseTest[56330:f803] dealloc
objc_release --> EXC_BAD_ACCESS in UIApplicationMain()
Of course, the code behaves identically if dot syntax is used (though dot syntax has the unfortunate effect of concealing the fact that it uses accessors, which is one reason I don't use it). I also tested by using an IBOutlet instead of instantiating in code. A lot more retain/releases, again unsurprisingly, but still a crash if I include an extra release:
2012-01-30 11:09:59.631 ReleaseTest[56389:f803] alloc
2012-01-30 11:09:59.633 ReleaseTest[56389:f803] retain
2012-01-30 11:09:59.634 ReleaseTest[56389:f803] retain
2012-01-30 11:09:59.635 ReleaseTest[56389:f803] retain
2012-01-30 11:09:59.636 ReleaseTest[56389:f803] retain
2012-01-30 11:09:59.636 ReleaseTest[56389:f803] retain
2012-01-30 11:09:59.637 ReleaseTest[56389:f803] release
2012-01-30 11:09:59.638 ReleaseTest[56389:f803] release
2012-01-30 11:09:59.638 ReleaseTest[56389:f803] release
2012-01-30 11:09:59.639 ReleaseTest[56389:f803] release
2012-01-30 11:09:59.640 ReleaseTest[56389:f803] release
2012-01-30 11:09:59.641 ReleaseTest[56389:f803] release
2012-01-30 11:09:59.641 ReleaseTest[56389:f803] dealloc
objc_msgSend --> EXC_BAD_ACCESS in UIApplicationMain()
So, in conclusion, I believe you do have a memory management error in your code, and this is responsible for some, if not all, of your crashes (which, I note, are of the same kind that I experimentally reproduced).
EDIT: This post is wrong and irrelevant to the OP. Struck-out the misleading text. Leaving rest here because I learned something in comments from Kevin below, and it may help someone else!
IF you don't need the webViewDidFinishLoad handling to occur on dealloc, set self.webView.delegate = nil; before calling stopLoading.
Also, you could manually call the webViewDidFinishLoad handling in the dealloc if any of it is needed and doesn't depend on the state of the webView.
I really wouldn't recommend putting a stopLoading in your dealloc. Just set the delegate to nil.
You should not design your dealloc to launch a lot of logic -- If your object is receiving a dealloc before its finished handling the logic you expect it to, then you are allowing it to be released it too quickly.

Releasing object in viewDidUnload?

In my app im calling presentModelViewController to present a new controller. Everytime this action is triggered memory is allocated. But somehow its not releases properly because at some point my app is using too much memory and it craches.
Probably this is because im not releasing the property objects not correctly ( or not at all ) Is the undermentioned the right way? The dealloc is probably, but what about the viewDidUnload?
- (void)viewDidUnload {
[_sushiTypes release];
_sushiTypes = nil;
}
- (void)dealloc {
[_sushiTypes release];
_sushiTypes = nil;
[super dealloc];
}
Don't forget the call to super in viewDidUnload. You should also access your instance variable through its setter to set it to nil in viewDidUnload. In dealloc, just release the instance variable directly.
- (void)viewDidUnload {
[super viewDidUnload];
NSLog(#"viewDidUnload being called");
self.sushiTypes = nil;
}
- (void)dealloc {
[_sushiTypes release];
NSLog(#"dealloc being called");
[super dealloc];
}
I don't think this is the source of your memory problems though. Does your modal view controller have any other instance variables or IBOutlets?
To help debug further, try using Instruments. From Xcode, go Product > Profile, then choose the Allocations template when Instruments opens. Then open and close your modal view controller multiple times, and inspect your leaks and allocations. If Allocations are growing each time you present/dismiss, then try clicking the 'Mark Heap' button before and after each present/dismiss. You can then inspect the objects that are being allocated and not deallocated with each cycle.
I think I found the problem. You should NEVER assign nil value in the dealloc function. The usual practice while releasing is going to viewDidUnload assign the nil value with the setter and then properly release the property in the dealloc method. Like this:
- (void)viewDidUnload {
self._sushiTypes = nil;
[super viewDidUnload];
}
- (void)dealloc {
[_sushiTypes release];
[super dealloc];
}
Remember to call the super in viewDidUnload like Matty said. You can refer to this SO question for more insight about this process. First set to nil and then release.

Obj-c initWithCoder seems to crash NavigationController

does anyone know why when i add...
-(id) initWithCoder: (NSCoder *) decoder {
OutputDataMutableArray = [[decoder decodeObjectForKey:#"RootViewControllerPostArray"] retain];
return self;
}
to my RootViewController it will not push a new view from a table cell using the didSelectRowAtIndexPath method. This is the line it seems to hang on...
[[self navigationController] pushViewController:postController animated:YES];
i get no error messages, it just seems to skip it. Unless i take the initWithCoder out and it works fine.
any insight would be appriciated.
Chris
You’re not calling the superclass’s implementation of -initWithCoder:.
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(self)
{
OutputDataMutableArray = [[decoder decodeObjectForKey:#"RootViewControllerPostArray"] retain];
}
return self;
}
Check the log and look for exceptions being thrown causing your code to terminate prematurely. Or set a break on NSException raise.
BTW, you need to be calling [super init] in initWithCoder:

Dealloc Being Called Twice?

Resloved!
Thanks to Lone Gunman, this issue was due to an oversight of not setting the many delegates to nil before releasing them.
This is a strange one... I'm familiar with basic memory management but I think something is unusual about what I am seeing. Here is a little background...
I have a NavigationController that handles the navigation between the following ViewControllers:
Home -> Games -> Game
When running the code it falls down when leaving the Game. Within the GameViewController there is a dealloc method that resembles:
- (void)dealloc
{
[board release];
[opponentsViewController release];
[instructionsViewController release];
[imgPicker release];
[gameView release];
[super dealloc];
}
When the navigation controller goes back to the Games list (from the Game) it throws a EXC_BAD_ACCESS. So I bring up my trusty profiler and check for Zombies. Alas, just as I expected a message is being sent to a deallocated object! Digging deeper I find there to be 3 entries in the object's history:
Board getting alloc'd (called by Game's init method)
Board getting released (called by Game's dealloc method)
Board being Zombie'd (called by Game's dealloc method)
Both calls 2 and 3 are called from UINavigationController setDisappearingViewController.
In my dealloc method I set breakpoints to each release call, when doing so - the [board release] call occurs, then the [opponentsViewController release] call occurs then the [board release] call occurs again. So I'm seeing the dealloc method does not finish completely and calls again.
What might be causing this?
Edit: This is the GameViewController Implementation
Code from the Games controller that adds this game:
-(void) gotoGame:(int)tag {
game = [[GameViewController alloc] init];
[self.navigationController pushViewController:game animated:YES];
[game release];
}
Edit: This is the GameViewController Header
I would try setting all your ivar's delegates to nil (EDIT: in dealloc). I've had a similar problem with a fetched results controller. Failed to set the its delegate to nil in dealloc and the core data stack still had a pointer to it when the view controller was released.
So that's my bet, set ivar delegates to nil in dealloc, although I can't see your header to know what protocols your are conforming to be sure.
EDIT: Explanation
Setting a delegate is actually giving the object that is doing the delegation a pointer (I believe it usually an assigned property).
#property (assign) delegate;
I'll use the problem I had as an example.
So let's say you have a view controller that has a fetchedResultsController as an ivar. When you set the FRCs delegate:
fetchedResultsController.delegate = self;
and the view controller gets released, any object that is using that pointer still thinks it's live. You would think since the FRC is getting released in dealloc as well, you'd be fine(which is why it took me 4 days to figure this out :) ), but sometimes other parts of an implementation use your delegate as well. So the fix is:
-(void)dealloc
{
self.fetchedResultsController.delegate = nil;
[_fetchedResultsController release];
[super dealloc];
}
Note: as soon as the new tools are available to everyone you won't have to worry about this stuff anymore ^ ^;
try
- (void) dealloc {
if(game != nil){
//release here
[game release];
}
[super dealloc];
}
By the way it seems you have declare game in header file and just after pushing you are releasing it and also in dealloc method you are releasing it. Either remove the release call from dealloc method or change you method like this.
-(void) gotoGame:(int)tag {
GameViewController *game = [[GameViewController alloc] init];
[self.navigationController pushViewController:game animated:YES];
[game release];
}
UPDATE
Also you are not using the tag anywhere. Why don't you create your init method like this
GameViewController *game = [[GameViewController alloc] initWithTag:tag];
[self.navigationController pushViewController:game animated:YES];
[game release];