Reusing UIWebView is causing crashes - ios

I've read that reusing UIWebViews is kind of a bad practice. Some code I inherited at work is pushing a variety of content to a UIWebView. Powerpoints, Word documents, and video. This all works just fine under normal circumstances. When we get to switching out the content in the UIWebView too fast, it dumps.
My webView is set as a property. It is hooked up in IB just fine. Normal selection from our tableView loads local content just fine. It takes rapid fire selection of either the same cell or combinations of multiple to get it to crash.
I can capture some error messages for it for the webViewDidFailWithError. But those will trigger even without a crash.
Here is the error localized string.
The operation couldn’t be completed. (NSURLErrorDomain error -999.)
When the app does finally crash, it blows up on this goofy WebCore error.
If anyone has any links or some code examples how to handle this I would appreciate it. Maybe an example how to best reuse my webView property without blowing things up.
I would load some of my code, but there is a lot going on not related to the webView itself. All content being pushed to the webView is done via [self.webView loadRequest:request]; with the request being an NSURLRequest filled with the path to the local content.
I will be very appreciative if anyone can help me out on this one. Fingers crossed for something simple.

I'm not certain this way is the "best" way to solve the issue, but it does seem to be working quite well. Short, sweet, and it works.
I disabled userInteraction with the tableView that updates the content in the webView. Since you have to mash on it so much, missing a tap here or there probably won't be missed. So for now, this is the fix.
#pragma mark -
#pragma mark UIWebViewDelegate methods
-(void)webViewDidStartLoad:(UIWebView *)webView {
[self.tableView setUserInteractionEnabled:NO];
}
-(void)webViewDidFinishLoad:(UIWebView *)webView {
[self.tableView setUserInteractionEnabled:YES];
}
// I re-enable on load failures as they can block you out entirely on fail
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
[self.tableView setUserInteractionEnabled:YES];
}

If it’s crashing only when you tap quickly, could you put a gesture recognizer over it to act as a poor man's rate limiter?

Are you using [webview stopLoading]; before loading another request? What you might need to do is cancel or stop the current loading before trying to load a different one. The other option being restrict the user input.

In your UIWebViewDelegate, you could implement webView:shouldStartLoadWithRequest:navigationType: to return NO if there is already a request loading.

I can't help but think you'd be better off not re-using the UIWebView. Ditch the nib, create it programmatically and set it to nil and re-create/re-assign/re-alloc it when the data source changes.
On a different note I would make sure to use NSOperationQueue for the data loading.

Related

bool _WebTryThreadLock(bool) crash

I'm seeing some crashes in my App crash tracking tool. Basically I have a tabBarController, one of the tabs has an embedded UIWebView, another tab has a controller with a UITableView. So what happens is that when user goes to the WebView first, and it starts loading, after a few seconds they go to the tableView controller, the crash randomly happens.
Judged by the timing I saw in crash log, it seems that the webView is about to finish loading when the user taps the UITableView tab, so I inserted a code into the didFinishLoading. This way I can reliably reproduce the crash. The code looks like this:
The webViewController:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[tabBarController gotoTableViewController];
}
The tableViewController:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView reloadData];
}
Here is the crash:
bool _WebTryThreadLock(bool), 0x1700113d0: Multiple locks on web thread not allowed! Please file a bug. Crashing now...
I don't know what I can do about it, it seems both the webView and tableView are locking the mainThread? Is there a way to "unload" the webView loading from main thread? I've tried stopLoading, not working...
Found it. It was indeed caused by the file parsing on mainThread. My file parsing is utilising NSRunLoop to parse a XML file. However the file is meant to be used as a cached values and needed to be parsed synchronously, so I put it on mainThread. I tried to put it onto a background thread and use dispatch_sync, the crash's still there. So I tried dispatch_async with a dispatch_semaphore_t, the crash was gone!
I had the same issue, and I also had an NSRunLoop that could potentially ran while the WebView was fetching the data.
Removing the NSRunLoop the problem is now fixed, but I don't understand why the problem happens in first place.
#KKRocks could you elaborate a bit why the NSRunLoop should cause the WebView to run in background?

UIWebview shouldStartLoadWithRequest delegate method gets called only once (inconsistent)

I'm loading a local html template with some js code into webview. It works most of the time. But sometimes, the shouldStartLoadWithRequest delegate gets called once and won't get called at all later. By the way, I'm seeing this issue more consistently in iOS 9.1 and above.
Whenever this issue occurs, if I recreate the webview and load same content, then it works fine. So, I wanted to know why it doesn't work the first time (inconsistent) but works the second time.
I'm calling
[_webView setDelegate:nil];
//and
[_webView stopLoading]; in dealloc method.
Would be helpful if anyone can suggest something.
I think somewhere your webView just loses its delegate. So it may just reset it with other one somewhere in code or just seed with nil. Looking to your posted code I can predict such situation - two instances has references to this webView. One instance should delete and it's calling dealloc method - webView delegate sets to nil, but other instance still using this same webView, but now it has delegate nil. As delegate is nil than webView won't send any message to him. It's only my prediction, still you'd should check if your delegate is nil or not and than begin new problem research. Good luck.

UIWebView taking lots of memory

In my app when i load UIWebView with any Website url the memory jumps from 30mb to around 140mb.
I am using ARC
and when dismissing the UIWebViewController[Viewcontroller which contains UIWebView], it doesnt releases the memory.
Can any body help me how to solve this memory issues as well as please also provide me pointers of memory bestpractices in ARC
For loading the webpage :-
NSURL *nsurl=[NSURL URLWithString:self.url];
NSURLRequest *nsrequest=[NSURLRequest requestWithURL:nsurl];
[webview loadRequest:nsrequest];
FOr Dismissing the Viewcontroller:-
[self dismissViewControllerAnimated:YES completion:^{
webview.delegate=nil;
webview=nil;
}];
Thanks in Advance :)
wow, today we also meet this problem. when we try to load a web page which contain lots pictures or a huge web page, it's easy to crash. we also find the memory used nearly 200M.
finally we find that we could remove the memory cache of web view
[[NSURLCache sharedURLCache] removeAllCachedResponses];
you can try it when you receive memory warning. or you can call it in the dealloc method.
if it still have some problems, try to limit the memory cache size by call
[[NSURLCache sharedURLCache] setMemoryCapacity:size]
good luck!
Don't listen to them,
Its not a problem, phones can handle all that memory usage, try using an MKMapView and watch your memory usage soar.
In your simulator, try putting through a low memory warning cmd+shift+m, and see what happens, you can't expect all web views to run at the same memory usage, also, you have give a chance for your phone to buffer it all out.
iPhones can handle all of that, so you shouldn't worry much about it.
But try this anyway:
The behaviour of UIWebViews changes if you uncheck "Detects Links" in the nib file, so try toggling that and see what happens.
Use Instruments Allocation tab to see the number of instances of your UIWebViewController live in memory. (Cmd + I in Xcode, let Instruments open, select Allocations, and type UIWebViewController in the search bar)
If there's more than what you expect it to be, lookout for a retain cycle.
Try to use weak references within blocks instead of self.
Make sure you aren't holding a strong reference to the UIWebViewController object at
more than one place.
Put a breakpoint/NSLog in the dealloc method your viewController to see if it is being called.
There is no sense to search for a problem until you are sure that problem exists.
There are two possibilities why memory usage is so high:
1.UIWebView is not deallocated.
2.It is some system caching or some specific iOS memory processing algorithm.
In second case you can do nothing. To check first case you can try to use instruments or just subclass UIWebView and add logging to dealloc method.
#implementation MyUIWebView
- (void)dealloc
{
NSLog(#"MyUIWebView deallocated!");
}
#end
Do not forget to change UIWebView to MyUIWebView in your code.
And only if you will be sure that UIWebView is not deallocated you should start to search for strong pointers, retain circles and so on.
add
if (webView.isLoading){
webView.stopLoading;
}
webView.delegate=nil;
before this line
[self dismissViewControllerAnimated:YES completion...]
nil ing your webView should be taken care of by ARC. Don't refer to the web view in the completion block.
I think you forgot to remove the UIWebView from the view hierarchy. This would cause an extra retain count and prevent the memory from being reclaimed when you nil'd out the property.
Just some things you should check:
A controller has a strong reference to its view, and this view retains all the subviews as long as you keep them inside the view hierarchy, so any additional properties should be weak, including your webView property.
When using ARC there's no need to "nil" everything. ARC will nil all its strong references at dealloc automatically.
No need to do anything with weak references neither. Moreover on iOS 5+ weak references become nil automatically if the referenced object gets deallocated somewhere else.
I think that most likely your whole view controller is getting retained somewhere, and thus its view and the webView.
Make sure the you only keep weak references to the controller and let it be retained by a container or presenting controller so it gets released automatically as long as it gets popped or dismissed.
I was having the same problem in my application. I would recommend that you should use WKWebView. WKWebView was introduced with iOS 8.
You can read more about it here
apple documentation
The memory issue is definitely solved. You will get all the functionality from webview and some more(i.e. progress). Hope this helps
If you want to reduce the memory usage of UIWerview immediately, you can try:
1.
[webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"about:blank"]]];
or
2.
[webview loadHTMLString: #"" baseURL: nil];
or
3.
[webview loadData:nil MIMEType:nil textEncodingName:nil baseURL:nil];
It worked to me.

`didFailLoadWithError:` Issues

I noticed a weird bug in my web view. I have an app that checks to see if the device is connected to the internet, if it is connected then the error image remains hidden. If it is not connected, I unhide the error image. The thing is, if I try and click on another link before the page is finished loading, didFailLoadWithError: fires and unhides the connection issue image. Here is the code I'm using:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"didFinish: %#; stillLoading:%#", [[webView request]URL],
(webView.loading?#"NO":#"YES"));
}
-(void)webView:(UIWebView *)myWebView didFailLoadWithError:(NSError *)error {
NSLog(#"No internet connection");
_connectionError.hidden = NO;
}
If I wait until I see "still loading" in the log, then press another link, all is well. If I press a link before I see that message, then didFailLoadWithError:fires and unhides the connection issue. I'm extremely new to Objective-C, and programming in general, so I have no idea how to fix this.
Is there any simple that I can have didFailLoadWithError: only run once when the app starts up?
The didFailLoadWithError is called everytime the web view failed to load, which happens when you try to load another page before the current page is finished loading.
You should try other methods to detect if there is no internet connection, such as using Reachability: http://developer.apple.com/library/ios/#samplecode/Reachability/Introduction/Intro.html
Another way is simply calling stopLoading in webView:shouldStartLoadWithRequest:navigationType: delegate method for your particular case of triggering didFailLoadWithError when user taps on another link while the previous link is still loading in the web view.
The solution that I used was to check for the error code. If the code equaled the connection error, then I unhid the error image.

UIWebView getting stuck after webViewDidStartLoad

Our iOS app has a Web View that is rendering pages from file: URLs. When the user touches an <A> element to switch pages, about 20% of the time, it gets stuck as follows:
User taps <A> link
We get WebView.shouldStartLoadWithRequest callback
We get WebView.webViewDidStartLoad callback
nothing happens after this
The screen still shows the original page with the link on it. We can break the logjam in two ways:
Rotate the device
Tap the screen
At that point, the page will immediately finish loading.
We used the recipe from here:
Javascript console.log() in an iOS UIWebView
to allow us some insight into the page load. We put the javascript-side stuff right in the first script file we load on the page, and it doesn't print its message until after we do the rotate-or-tap workaround.
So it appears that it is getting stuck somewhere between starting to load the page, and starting to evaluate the stuff on the page.
We have tried a number of work-around, none of which helped:
Setting location.href instead of using the tag
Setting location.href from javascript timeout
In the didStartLoad callback, created a thread that called setNeedDisplay on the webView over and over
Any idea what we might be doing wrong?
You could use gdb within XCode to debug the problem. I think canidates for messages that could have break points are:
- (void)webViewDidStartLoad:(UIWebView *)webView
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
I'd also add a break in whatever UIViewController is showing your UIWebView for:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
Then you could step through and hopefully catch what's wrong with your UIWebView subclass.
If you don't want to add breakpoints you could just run your app in the XCode debugger and hit the "pause execution" button above the console window when your app becomes unresponsive while loading.
It's hard to know what's going on without any code to go off of but I bet Xcode can help you find the issue quickly.
Good luck!
Found the solution for this weird behavior, at least in my case.
In my App i've created a subclass of UIWebView. I know that Apples documentation notes that you should not subclass UIWebView. But it's "necessary" in my App.
The problem was, that i've overwritten scrollViewDidEndDragging:willDecelerate: without calling super.
- (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// my code
}
becomes to
- (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
[super scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
// my code
}
Now it always loads the web site when tapping on a html link.

Resources