72019

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?

Answer1:

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.

Answer2:

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.

Answer3:

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).

Answer4:

<strong>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!</strong>

<hr>

<strike>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. </strike>

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.

Recommend

  • Centering a specific element among others with flexbox [duplicate]
  • Is there a way to pivot a customer ID and a their most recent order dates?
  • How to load more than one div at a time
  • Redshift Querying: error xx000 disk full redshift
  • Bash if statement with multiple conditions
  • How to remove a SwiftyJSON element?
  • Position: fixed nav does not stay fixed
  • How to determine if there are bytes available to be read from boost:asio:serial_port
  • Debug.DrawLine not showing in the GameView
  • Visual Studio 2010 debugger build correctly - compiler pdb and linker pdb not in synch?
  • Swift: Switch statement fallthrough behavior
  • Custom Tabgroup Appcelerator
  • Unity3D & Android: Difference between “UnityMain” and “main” threads?
  • Google Custom Search with transparent background
  • Change JButton Shape while respecting Look And Feel
  • Why HTML5 Canvas with a larger size stretch a drawn line?
  • Spray.io: When (not) to use non-blocking route handling?
  • recyclerView does not call the onBindViewHolder when scroll in the view
  • TFS: Get latest causes slow project reloading
  • Java applet as stand-alone Windows application?
  • Controls, properties, events and timers running in design time
  • output of program is not same as passed argument
  • Perl system calls when running as another user using sudo
  • Where to put my custom functions in Wordpress?
  • jQuery tmpl and DataLink beta
  • Jquery - Jquery Wysiwyg return html as a string
  • Why winpcap requires both .lib and .dll to run?
  • Akka Routing: Reply's send to router ends up as dead letters
  • SVN: Merging two branches together
  • Hibernate gives error error as “Access to DialectResolutionInfo cannot be null when 'hibernate.
  • How do I rollback to a specific git commit
  • SQL merge duplicate rows and join values that are different
  • How to set the response of a form post action to a iframe source?
  • How to CLICK on IE download dialog box i.e.(Open, Save, Save As…)
  • Can Visual Studio XAML designer handle font family names with spaces as a resource?
  • Are Kotlin's Float, Int etc optimised to built-in types in the JVM? [duplicate]
  • unknown Exception android
  • Checking variable from a different class in C#
  • Reading document lines to the user (python)
  • Unable to use reactive element in my shiny app