Reputation: 53
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?
Upvotes: 2
Views: 2069
Reputation: 5703
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.
Upvotes: 0
Reputation: 185663
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.
Upvotes: 3
Reputation: 8808
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).
Upvotes: 0
Reputation: 34902
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.
Upvotes: 0