user2200321
user2200321

Reputation: 445

Horrible crash after trying to use MBProgressHud

I'm using MBPogressHUD in my project and I have all the libraries, imports, etc. set up correctly. However, I'm getting a crash that seems like it has to do with the selector methods. I have a method called "getInfo" that basically connects to a server. The HUD is triggered by pressing a button. After doing some research on the crash, people said to put the initialization in viewWillAppear because some of the init time takes up the time it takes to actually do the task and show the HUD.

    -(void)viewWillAppear:(BOOL)animated {
    connectionHUD = [[MBProgressHUD alloc]initWithView:self.view];
    [self.view addSubview:connectionHUD];
    connectionHUD.labelText = @"Connecting";
    connectionHUD.mode = MBProgressHUDModeAnnularDeterminate;    
}

-(IBAction)connectButton:(id)sender {


    [connectionHUD showWhileExecuting:@selector(getInfo) onTarget:self withObject:nil animated:YES];
}

Crash:

Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...



-(void)getInfo {

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
UserPi *newPi = [[UserPi alloc]init];
newPi.passWord = self.passTextField.text;
[defaults setObject:self.passTextField.text forKey:@"password"];
newPi.userName = self.userTextField.text;
[defaults setObject:self.userTextField.text forKey:@"username"];
newPi.ipAddress = self.ipTextField.text;
[defaults setObject:self.ipTextField.text forKey:@"ip"];
[newPi connectToServer];
NSString* newAddress = [newPi returnIP];
self.connected = newPi.connected;
[self.delegate sendIP:newAddress];
[self.delegate isConnected:self.connected];
[defaults synchronize];
[self.navigationController popToRootViewControllerAnimated:YES];


}

full error:

bool _WebTryThreadLock(bool), 0x7327740: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
1   0x3a1ffe9 WebThreadLock
2   0x4ec8ff -[UITextRangeImpl isEmpty]
3   0x4ec4db -[UITextRange(UITextInputAdditions) _isCaret]
4   0x48e7b6 -[UITextSelectionView setCaretBlinks:]
5   0x328f79 -[UIKeyboardImpl setCaretBlinks:]
6   0x3185bc -[UIKeyboardImpl setDelegate:force:]
7   0x3184ae -[UIKeyboardImpl setDelegate:]
8   0x53ff65 -[UIPeripheralHost(UIKitInternal) _reloadInputViewsForResponder:]
9   0x29215b -[UINavigationController navigationTransitionView:didStartTransition:]
10  0x418961 -[UINavigationTransitionView transition:fromView:toView:]
11  0x418658 -[UINavigationTransitionView transition:toView:]
12  0x294651 -[UINavigationController _startTransition:fromViewController:toViewController:]
13  0x29489b -[UINavigationController _startDeferredTransitionIfNeeded:]
14  0x295dc6 _popViewControllerNormal
15  0x296065 -[UINavigationController _popViewControllerWithTransition:allowPoppingLast:]
16  0xe6124b0 -[UINavigationControllerAccessibility(SafeCategory) _popViewControllerWithTransition:allowPoppingLast:]
17  0x2961a8 -[UINavigationController popViewControllerWithTransition:]
18  0x2965b9 -[UINavigationController popToViewController:transition:]
19  0x296257 -[UINavigationController popToRootViewControllerWithTransition:]
20  0x2961de -[UINavigationController popToRootViewControllerAnimated:]
21  0x4868 -[ConnectionViewController getInfo]
22  0x126a6b0 -[NSObject performSelector:withObject:]
23  0x8657 -[MBProgressHUD launchExecution]
24  0xca1805 -[NSThread main]
25  0xca1764 __NSThread__main__
26  0x95108ed9 _pthread_start
27  0x9510c6de thread_start

Upvotes: 0

Views: 716

Answers (3)

abarnert
abarnert

Reputation: 365707

The problem is that your getInfo method makes calls into the UI, like this:

[self.navigationController popToRootViewControllerAnimated:YES];

… but you're running it on a background thread.

The whole point of -showWhileExecuting: onTarget: withObject: animated: is that it automatically runs your code in a background thread.

So, you need to protect things the same way as you do when manually running in a background thread.

So, any UI code in your method needs to use performSelectorOnMainThread: and friends, or dispatch_async or other means of doing the same thing.

In this particular case, you want to dispatch a method that takes a primitive (BOOL) argument, which means you can't just use -performSelectorOnMainThread: withObject:. But you also presumably want to wait until it's done, which means you can't just use dispatch_async.

You can just write a wrapper method that takes no arguments:

- (void)popNavigationControllerToRoot {
    [self.navigationController popToRootViewControllerAnimated:YES];
}

… and then:

[self performSelectorOnMainThread:@selector(popNavigationControllerToRoot)
                    waitUntilDone:YES];

Or you can use wrappers around NSInvocation, like the ones here:

[[self.navigationController dd_invokeOnMainThreadAndWaitUntilDone:YES] 
 popToRootViewControllerAnimated:YES];

Upvotes: 0

Ken
Ken

Reputation: 31161

Are you calling -getInfo on the main thread? [self.navigationController popToRootViewControllerAnimated:YES]; must be called on the main thread.

Upvotes: 1

Sonny Saluja
Sonny Saluja

Reputation: 7287

Most UIKit framework methods are not thread safe and must always be called on the main thread. In your case, getInfo is calling UIKit APIs from a background thread notably, -[UINavigationController popToRootViewControllerAnimated:]

MBProgressHUD causes your getInfo to be called on a background thread. See the method showWhileExecuting:onTarget:withObject:animated:.

Try using GCD to dispatch the UIKit methods on the main thread:

-(void)getInfo {    
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    UserPi *newPi = [[UserPi alloc]init];
    newPi.passWord = self.passTextField.text;
    [defaults setObject:self.passTextField.text forKey:@"password"];
    newPi.userName = self.userTextField.text;
    [defaults setObject:self.userTextField.text forKey:@"username"];
    newPi.ipAddress = self.ipTextField.text;
    [defaults setObject:self.ipTextField.text forKey:@"ip"];
    [newPi connectToServer];
    NSString* newAddress = [newPi returnIP];
    self.connected = newPi.connected;     
    // **NOTE**   
    // PS: self.delegate methods should not call UIKit methods
    // if they do, then move them into the main thread callback block
    [self.delegate sendIP:newAddress];
    [self.delegate isConnected:self.connected];
    [defaults synchronize];

    // Do UI work on main thread.
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.navigationController popToRootViewControllerAnimated:YES];    
    });
}

Upvotes: 1

Related Questions