Monday, 14 November 2011

UIWebView and EXC_BAD_ACCESS

When using a UIWebView, if it is released before loading the full content, an EXC_BAD_ACCESS will be aroused. The following article gives a solution to the problem.

From: http://www.touch-code-magazine.com/uiwebview-and-exc_bad_access/

It is great that in Cocoa Touch you can embed a UIWebView in your app and show web content. It’s basically the same view which safari uses to render web pages, so you get years of web rendering development for free.

UIWebViewDelegate

If you want to use UIWebView the first thing to do is have a look at UIWebViewDelegate protocol:

@protocol UIWebViewDelegate 
 
@optional
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
 
@end
Quite straightforward to use : you set your viewcontroller (or any other object) to be the delegate for the UIWebView and you can optionally implement
  • shouldStartLoadWithRequest – you can check the NSURLRequest to see which is the url being request, what parameters, etc. This one is useful to actually check the links the user is clicking inside a page. For example if the user has clicked a link “javascript:window.close()” it does not make sense to execute it inside the UIWebView, but you can react in some other way in your app UI
  • webViewDidStartLoad – an HTTP request has been started – great place/time to show an activity indicator
  • webViewDidFinishLoad – a page has been loaded (including all resources like images, etc) – that’s a great moment to hide your activity indicator
  • didFailLoadWithError – error handling


Threading and EXC_BAD_ACCESS

As you can imagine there are several layers of workers in a component as complicated as a web browser – even if you don’t see everything happening behind the curtain – it’s all there, html needs to be parsed, images need to be loaded, css to be applied, etc. etc.
Apple lets you set your UIWebView delegate and be notified when all the work has been done, but if you have some UIWebView controllers inside a navigation controller 0r just more complicated UI you can bump into some sporadic EXC_BAD_ACCESS errors.
The delegate property of UIWebView is non- owning, i.e. it has the assign accessor, thus when the working thread is finished to load all html, css, images, etc. is sends the webViewDidFinishLoad message to the whatever it finds in the delegate property, but it has no way to tell whether the delegate hasn’t been released yet. There you go, if your delegate has been already released (for example you went away from the screen where the web page was supposed to be displayed) – you get a message sent to a released object.
The solution is to set UIWebView’s delegate to nil. The webViewDidFinishLoad message is still sent to it, but since you can send messages to nil it’s actually no problem.
For example in your ViewController class :

- (void)dealloc {
 //very important
 webView.delegate = nil;
 [super dealloc];
}
That’s it.