UIWebView not freeing all live bytes using ARC

Go To StackoverFlow.com

9

I am currently building a navigation controller app in iOS 5.1 that uses ARC. I often need to display webpages and I have made a web viewer that is just a UIWebView with some custom content around the sides. When the user is finished looking at the page they hit the back button which should release all of the memory associated with the custom web viewer. My problem is that all of the memory does not appear to be released when the back button is hit. I have built a toy app (on github) that is just a couple of buttons each having a first responder that calls a different page.

@implementation ViewController
-(IBAction)googlePressed:(id)sender
{
   CustomWebView *customWebView = [[CustomWebView alloc] initWithStringURL:@"http://www.google.com"];
   [self.navigationController pushViewController:customWebView animated:NO];
 }
-(IBAction)ksbwPressed:(id)sender
{
   CustomWebView *customWebView = [[CustomWebView alloc] initWithStringURL:@"http://www.ksbw.com/news/money/Yahoo-laying-off-2-000-workers-in-latest-purge/-/1850/10207484/-/oeyufvz/-/index.html"];
   [self.navigationController pushViewController:customWebView animated:NO];
}
-(IBAction)feedProxyPressed:(id)sender
{
   CustomWebView *customWebView = [[CustomWebView alloc] initWithStringURL:@"http://feedproxy.google.com/~r/spaceheadlines/~3/kbL0jv9rbsg/15159-dallas-tornadoes-satellite-image.html"];
   [self.navigationController pushViewController:customWebView animated:NO];
}
-(IBAction)cnnPressed:(id)sender
{
   CustomWebView *customWebView = [[CustomWebView alloc] initWithStringURL:@"http://www.cnn.com/2012/04/04/us/california-shooting/index.html?eref=rss_mostpopular"];
   [self.navigationController pushViewController:customWebView animated:NO];
}

The CustomWebView is just a UIWebView linked in IB to UIWebView Property

@implementation CustomWebView

@synthesize webView, link;

- (id)initWithStringURL:(NSString *) stringURL
{
   if (self = [super init]) {
       link = stringURL;
   } 
   return self;
}


- (void)viewDidLoad
{
   [super viewDidLoad];
   NSURL *url = [NSURL URLWithString:link];
   NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
   [webView loadRequest:urlRequest];
}

- (void)viewDidUnload
{
   [super viewDidUnload];
}

My problem is that I set the heap baseline to be the initial ViewController once everything is loaded. I then check the heap after loading a page then returning to the ViewController and get the following heapshots:

Heapshots

Which shows that after each series of clicking on a button and returning to the ViewController the heap continues to grow even though the CustomWebView should all be released.

EDIT:

The sample project described above can be found on github

2012-04-04 20:02
by brendan
Have you made sure Zombies are disabled - MishieMoo 2012-04-11 20:34
Yes, Zombies are disable - brendan 2012-04-11 21:26
Shot in the dark but try making your IBOutlet properties (weak) - wvteijlingen 2012-04-13 12:20


8

I had the same-ish problem, the i found this little piece of magic

/*

 There are several theories and rumors about UIWebView memory leaks, and how
 to properly handle cleaning a UIWebView instance up before deallocation. This
 method implements several of those recommendations.

 #1: Various developers believe UIWebView may not properly throw away child
 objects & views without forcing the UIWebView to load empty content before
 dealloc.

 Source: http://stackoverflow.com/questions/648396/does-uiwebview-leak-memory

 */        
[webView loadHTMLString:@"" baseURL:nil];

/*

 #2: Others claim that UIWebView's will leak if they are loading content
 during dealloc.

 Source: http://stackoverflow.com/questions/6124020/uiwebview-leaking

 */
[webView stopLoading];

/*

 #3: Apple recommends setting the delegate to nil before deallocation:
 "Important: Before releasing an instance of UIWebView for which you have set
 a delegate, you must first set the UIWebView delegate property to nil before
 disposing of the UIWebView instance. This can be done, for example, in the
 dealloc method where you dispose of the UIWebView."

 Source: UIWebViewDelegate class reference    

 */
[webView setDelegate:nil];


/*

 #4: If you're creating multiple child views for any given view, and you're
 trying to deallocate an old child, that child is pointed to by the parent
 view, and won't actually deallocate until that parent view dissapears. This
 call below ensures that you are not creating many child views that will hang
 around until the parent view is deallocated.
 */

[webView removeFromSuperview];
2012-07-17 11:37
by Daniel Magnusson
http://www.codercowboy.com/code-uiwebview-memory-leak-prevention - LordParsley 2014-08-12 10:02
I use this approach in an app that pushes several web views onto the navigation stack, but it does not work. I do the above when I receive memory warnings (clearing web views further back in the stack, then recreating them when the view reappear), when the parent view controller is disposed etc, but it does not work. First off all, memory consumption goes up when popping off the stack. Also, when returning after a rather deep navigation, where I am sure that every web view is deallocated as above, the app still consumes much of the totally allocated memory - Daniel Saidi 2014-10-16 09:35


1

I have been using ARC for a while now and I have found in the past that the web view will hold onto whatever was loaded until its told to load something else. The way I get around this is to use a javascript call in the view will (or Did) dissapear method like so:

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.webView loadHTMLString:@"<html></html>" baseURL:nil];
}

This releases the memory for me.

2012-04-11 20:27
by RachelD
I added this code to my project and it did not fix the memory issue. A sample project that shows my issue can be found https://github.com/bfruin/WebViewMemor - brendan 2012-04-11 21:57


1

UIWebView is a memory pig and doesn't deallocate memory well. I put a -(void) dealloc call in your CustomWebView class and it's called as expected when the Back button is hit.

You can try [[NSURLCache sharedURLCache] removeAllCachedResponses] or use and then free your own copy of NSURLCache, but frankly UIWebView never fully cleans up after itself.

2012-04-15 13:30
by EricS


0

I havent used ARC yet, but looking at your custom webview code, I think your webviews that you created are still alive.

if you want to be sure that your webviews are killed properly, make sure that in the viewWill/DidDisappear, call [webview stopLoading], and nil the webview.

(caution: if you are pushing another view, then also the webview will be killd, so make sure to handle those issues well)

Most often, I have had issues with webViews where even after popping the viewController, a call back to some webView delegate method would crash the app. (I guess ARC prevents it from crashing).

Let me know if this helps.

2012-04-09 23:01
by Nitin Alabur
Calvin, thank you for the reply, but calling stopLoading and nil'ing the webview does not change the heapshot or the total live bytes. For these intensive pages, would it be better to just open in Mobile Safari? This example project demoed above shows that it is very easy to prove that the UIWebView leaves a memory footprint even after everything is deallocated - brendan 2012-04-10 17:53
Webviews are expensive in terms of resource consumption. I am not sure why your total live bites are not changing. In your ViewController IBActions, after pushing the viewcontrollers on your nav stack, are you releasing the objects you are creating? I missed that detail earlier - Nitin Alabur 2012-04-10 20:07
ideally it should be-(IBAction)googlePressed:(id)sender { CustomWebView *customWebView = [[CustomWebView alloc] initWithStringURL:@"http://www.google.com"]; [self.navigationController pushViewController:customWebView animated:NO]; [customWebView release]; - Nitin Alabur 2012-04-10 20:09
I am using ARC which forbids message sends to release. From my understanding of ARC, it will be automatically called - brendan 2012-04-10 20:45
Not sure how i missed the ARC detail in your question. Now I am not sure, why it is not releasing - Nitin Alabur 2012-04-10 23:02
I have posted a sample project to show the issue: http://github.com/bfruin/WebViewMemor - brendan 2012-04-12 00:26
Tested it in instruments and searched for 'UIWebView' objects. They do go away when hitting the back button.. - Christian Schnorr 2012-04-15 10:46


0

Brendan a couple of points:

You have not posted enough code to be sure of what is happening. But why not have the controller own a SINGLE UIWebView and rather than creating another one on each button press simply pass it a string with the relevant URL? That is a better design pattern that will guarantee that you never have more than one around.

2012-04-11 13:23
by Cliff Ribaudo
I have tried with only one in the way specified and I get the same problems. I have posted a sample project on github to show my memory issues.

https://github.com/bfruin/WebViewMemor - brendan 2012-04-11 21:49

Well I took a quick look at your code on Git and nothing obvious, but I guess I have to say in the final analysis: what is the issue? You are using ARC. Memory management is NOT your issue. Unless you are seeing a leak, which is NOT indicated in the screen shot you posted what actually is the problem? Try using the Allocations tool and seeing what objects are instantiated and their counts - Cliff Ribaudo 2012-04-12 01:02
My issue is that the code posted on git displays that I do not get all the memory back upon returning from a view controller that has a web view. If the app were just a web viewer this may be okay, but this is a minor part of the app and I eventually get low memory warnings over time. I would prefer to avoid these memory issues, but would it be fine to just save the state of an app in a viewDidUnload and not worry as long as there are no leaks - brendan 2012-04-12 02:06
Well if you are running into low memory issues elsewhere and at other times then there may be some other memory issue closer to where/when you get that. You didn't mention that earlier. I would start by having only ONE UIWebView in this Controller and passing it the URL strings and take it from there. The bottom line ARC or not in iOS is really quite simple with respect to memory. If you are responsible for freeing it then it IS your problem, but when not, then NOT - Cliff Ribaudo 2012-04-12 03:07


0

@interface WebViewViewController : UIViewController<UIWebViewDelegate>

- (void)viewDidLoad
{
[super viewDidLoad];
NSString* urlAddress = @"http://www.google.com";
NSURL *url = [NSURL URLWithString:urlAddress];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
self.webView.delegate = self;
[self.webView loadRequest:requestObj];
}

-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[_webView stopLoading];
[_webView loadHTMLString:@"<html><head></head><body></body></html>" baseURL:nil];
[_webView stopLoading];
[_webView removeFromSuperview];
}

- (void)dealloc
{
[_webView stopLoading];
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
[_webView stopLoading];
_webView = nil;
}
2014-07-12 17:56
by Ssemmandarobert
Ads