Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to safely shut down a loading UIWebView in viewWillDisappear?

I have a view containing a UIWebView which is loading a google map (so lots of javascript etc). The problem I have is that if the user hits the 'back' button on the nav bar before the web view has finished loading, it is not clear to me how to tidily tell the web view to stop loading and then release it, without getting messages sent to the deallocated instance. I'm also not sure that a web view likes its container view disappearing before it's done (but I've no choice if the user hits the back button before it's loaded).

In my viewWillDisappear handler I have this

map.delegate=nil; [self.map stopLoading]; 

this seems to handle most cases OK, as nil'ing the delegate stops it sending the didFailLoadWithError to my view controller. However if I release the web view in my view's dealloc method, sometimes (intermittently) I will still get a message sent to the deallocated instance, which seems to be related to the javascript running in the actual page, e.g.:

-[UIWebView webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:]: message sent to deallocated instance 0x4469ee0 

If I simply don't release the webview, then I don't get these messages though I guess I'm then leaking the webview.

If I don't send the 'stopLoading' message, and simply release the webview within viewWillDisappear, then I see messages like this:

/SourceCache/WebCore/WebCore-351.9.42/wak/WKWindow.c:250 WKWindowIsSuspendedWindow:  NULL window. 

Possibly related, I sometimes (again totally intermittent) get an ugly heisenbug where clicking the back button on some other view's navbar will pop the title, but not the view. In other words I get left with the title of view n on the stack, but the view showing is still view n+1 (the result is you're trapped on this screen and cannot get back to the root view - you can go the other direction, i.e. push more views and pop back to the view that didn't pop corrrectly, just not to the root view. The only way out is to quit the app). At other times the same sequence of pushes and pops on the same views works fine.

This particular one is driving me nuts. I think it may be related to the view disappearing before the web view is loaded, i.e. in this case I suspect it may scribble on memory and confuse the view stack. Or, this could be completely unrelated and a bug somewhere else (i've never been able to reproduce it in debug build mode, it only happens with release build settings when I can't watch it with gdb :-). From my debug runs, I don't think I'm over-releasing anything. And I only seem to be able to trigger it if at some point I have hit the view that has the web view, and it doesn't happen immediately after that.

like image 332
frankodwyer Avatar asked Apr 26 '09 00:04

frankodwyer


2 Answers

A variation on this should fix both the leaking and zombie issues:

- (void)loadRequest:(NSURLRequest *)request {     [self retain];     if ([webView isLoading])         [webView stopLoading];     [webView loadRequest:request];     [self release]; } - (void)webViewDidStartLoad:(UIWebView *)webView {     [self retain]; } - (void)webViewDidFinishLoad:(UIWebView *)webView {     [self release]; } - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {     [self release]; }  - (void)viewWillDisappear {     if ([webView isLoading])         [webView stopLoading]; }  - (void)dealloc {     [webView setDelegate:nil];     [webView release];     [super dealloc]; } 
like image 94
rpetrich Avatar answered Oct 07 '22 08:10

rpetrich


There's a few ways to handle it, but this should work. You want the didFailLoadWithError message, it's what tells you it's stopped.

Set a flag isLeaving=YES; Send the Webview a stopLoading.

In didFailLoadWithError:, check for the error you get when the webview stops:

if ((thiserror.code == NSURLErrorCancelled) && (isLeaving==YES)) {

[otherClass performSelector:@selector(shootWebview) withObject:nil withDelay:0]

}

release the webView in shootWebview:


variations: if you want to be cavalier about it, you can do the performSelector:withObject:withDelay: with a delay of [fillintheblank], call it 10-30 seconds without the check and you'll almost certainly get away with it, though I don't recommend it.

You can have the didFailLoadWithError set a flag and clean it up somewhere else.

or my favorite, maybe you don't need to dealloc it all when you leave. Won't you ever display that view container again? why not keep it around reuse it?

Your debug being different from release issue, you might want to check your configuration to make sure that it's exactly the same. Bounty was on the reproducible part of the question, right? ;-).

-- Oh wait a second, you might be taking a whole View container down with the WebView. You can do a variation on the above and wait to release the whole container in shootWebView.

like image 21
dieselmcfadden Avatar answered Oct 07 '22 08:10

dieselmcfadden