bool _WebTryThreadLock(bool) crash - ios

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?

Related

EXC_BAD_ACCESS on observing table view contentSize value

Ran into an interesting error today and wondering how I can address it or make this code more robust so that my app doesn't crash. 99% of the time this code works fine and does exactly what I want it to do, but 1% of the time it crashes and Xcode just shows an EXC_BAD_ACCESS error.
Off the top of my head, I am thinking that the app is crashing because I am observing a property that really isn't made to be observed. Any suggestions/solutions?
As mentioned in comments, KVO typically does not run on main thread.
So most likely, when your value observation call back is called in Swift, you need to dispatch your assignment to main thread as you're changing a UI property of the View Controller.
Wrap your statement in a dispatch to main thread like so:
DispatchQueue.main.async {
// your code here
}

iPhone UI Updating while Xcode paused at breakpoint

I have an odd UI bug I am tracking down, where my UI in a particular UIViewController subclass shows up mostly ok and then animates itself to be totally ok.
I have tracked down where the "shift" is occurring, but have not yet completely solved the issue. While attempting to track down and fix the bug, I had a very, very odd thing happen.
I set and hit a breakpoint in -(void)viewDidAppear:(BOOL)animated for the UIViewController in question. When the breakpoint was hit, the UI on the attached phone was wrong. Then, while still on the breakpoint, without me taking any action, the UI performed the "shift" to correct the out-of-position frames.
How is this possible? Shouldn't -(void)viewDidAppear:(BOOL)animated fire on the main/UI thread? If so, how is any adjustment being made to the UI while paused?

Long-running task in application:didFinishLaunchesWithOptions:

I have some long-running startup tasks (like loading Parse objects from local data storage) in my app. Those tasks should be finished before the interface starts to appear. The app was originally created using storyboards, so the interface starts to appear automatically after application:didFinishLaunchesWithOptions: method finishes. I can't block main thread because Parse SDK fires all it's callbacks on main thread (so blocking results in deadlock). I also need to delay return from application:didFinishLaunchesWithOptions: to finish setup. So what I did is:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Dispatch long-running tasks
dispatch_group_t startup_group = dispatch_group_create();
dispatch_group_async(startup_group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Perform some long-running setup here
});
// Run main run loop until startup tasks finished (is it OK to do so?)
while (dispatch_group_wait(startup_group, DISPATCH_TIME_NOW))
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
return YES;
}
Is it the proper usage of NSRunLoop? Are there any potential caveats? Can you propose more elegant (preferably GCD) solution?
UPDATE:
Loading Parse object from local data storage (i.e. loading tiny file from SSD) is not so long operation as loading something from the web. The delay is barely noticeable, but long enough to trigger warnBlockingOperationOnMainThread, so UX is not an issue.
The real issue is (infrequent) crashes caused by spinning another main runloop above regular main runloop, which sometimes leads to reentering UIApplication delegate methods, which apparently is not thread-safe.
Of cause introduction of splash screen is an obvious solution, but I was looking for a something more simple and elegant. I have a feeling it exist.
While this technically works depending on what your setup is doing (for example web service calls) the application may never launch or launch with unexpected results.
The more user friendly method would be to add a "loading" view controller to your storyboard which would be your landing view. Perform your long running setup here while providing the user with information/status/whatever is appropriate, then push your original view controller.
Imho I think it's better to do your stuff in your main View Controller and show something to user like a custom spinner: in this way you can download your data and the user know what happens.
It's never a good idea take so long time to launch an app with a blank screen or just a launch screen with no info for the user.
This is creative but a really bad idea. You should make your UI load in a failsafe state - in other words, assume the network is not available/slow, and show a view that won't explode if it doesn't get data before being rendered. Then you can safely load data on a background queue and either update the view or transition to the data-centric view once it is done loading.
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
//Load your data here
//Dispatch back to the main queue once your data is loaded.
dispatch_async(dispatch_get_main_queue(), ^{
//Update your UI here, or transition to the data centric view controller. The data loaded above is available at the time this block runs
});
});
Lagging the return of didFinishLaunchingWithOptions: is really a bad idea.This will give the bad impression of your app and its a bad design.
Instead you could land in the view controller and show a overlay view that shows the progress or activity you are doing when completed you could dismiss it.This is the right approach.
Here is a link to Third party library that eases showing/hiding the overlay view and setting some text on the view.
You can find so many libraries like this just google iOS HUD.HUD means Heads Up Display or you can design the custom thing yourself using UIAnimation easily.
Generally speaking,
I have to appreciate that code you have written in the sense you have stopped the current flow or run loop using While and NSRunloop.
May be you can alter your code little bit and use in some other place of your program to stop the current flow or runloop and wait for something.
NSDate *runloopUntill = [NSDate dateWithTimeIntervalSinceNow:0.1];
while (YourBoolFlag && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode runloopUntill])
The following apple documentation will give you deep insights in NSRunloop 1 2

UIViewController animations stop working

My app runs fine in iOS6, but in an unspecified upcoming version of iOS that I cannot name for NDA reasons, all UIViewController transition animations stop working. New views just pop into place instantly. I am not sure if this unspecified future version of iOS is the cause, as I've seen this happen occasionally in iOS6.
Sometimes animations start working for a while and then stop shortly after, making me think it's some sort of memory warning issue, but my app is using a fairly reasonable ~125MB of RAM at most times. Can anyone offer any advice or things to investigate?
The described behavior has always existed: if you do work on background threads and then call and UIKit methods then more often than not the update will be delayed in a weird way.
Because of this you should always dispatch_async onto the main queue to update the UI.
Those bugs are very hard to catch since they do not always occur predictably.
To catch them I built a method that swizzles some UIKit methods to check if they are called on the main thread. This allows you to stop on a symbolic breakpoint, whenever you have forgotten to dispatch back onto main queue.
https://github.com/Cocoanetics/DTFoundation/blob/develop/Core/Source/iOS/Debug/UIView%2BDTDebug.m
A good workaround from the Apple dev forums on this issue:
Do this:
[UIView setAnimationsEnabled:YES]
And animations start working again. I suspect that this is either a straight up iOS7 bug, or somewhere in my code an animation or UIViewController launch is happening on a background thread, causing animations to stop. Probably unrelated to the unspecified future version of iOS.
This issue appears to be caused by doing UIKit stuff in background threads. I have a pre-render cache full of NSOperations that renders complex UIViews to UIImages to cache the output. This seemed to work fine in iOS6, but probably does cross the line somewhat. I'll need to replace this functionality with something that renders images and text to a graphics buffer rather than using UIViews and UILabels at all.
All you have to do is catch hold of main queue while updating UI on receiving response from an API.Ios uses main queue by default for updating UI but it is not 100 percent efficient.Hence you have to make sure that the UI gets updated on main thread only and the way to do that is as below:
DispatchQueue.main.async{
//UI related code eg:
self.label.text = "abc"
self.button.setTitle("xyz",.normal)
self.tableView.reloadData()
}
If you are not catching hold of main thread animations may or may not work.
But if you are using main thread animations will definetely work.
Correct Code while updating UI on api response:
Alamofire.getApiCall(paramaters: parameters, completion:{
response in
// UI related code.
DispatchQueue.main.async{
self.label.text = "abc"
self.button.setTitle("xyz",.normal)
self.tableView.reloadData()
}
})
Incorrect Code which may cause animations to stop and lead to weird crashes:
Alamofire.getApiCall(paramaters: parameters, completion:{
response in
// UI related code.
self.label.text = "abc"
self.button.setTitle("xyz",.normal)
self.tableView.reloadData()
})

Reusing UIWebView is causing crashes

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.

Resources