This is by far the weirdest problem I've been stuck with.
I have a UIViewController on a UINavigationController and I want to call a method at viewDidAppear using NSInvocationOperation so it can run on a back thread when the view becomes visible.
The problem is that if I pop the view controller BEFORE the operation (in this case the testMethod method) completes running, the app crashes.
Everything works fine if I pop the view controller AFTER the operation runs it's course.
When the app crashes, it stops at [super dealloc] with "EXC-BAD-ACCESS" and gives me the following error.
bool _WebTryThreadLock(bool), xxxxxxxxx: 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...
And this is my code (super simplified)..
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSInvocationOperation *theOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testMethod) object:nil];
[operationQueue addOperation:theOperation];
[theOperation release];
}
- (void)testMethod
{
NSLog(@"do some stuff that takes a few seconds to complete");
}
- (void)dealloc
{
[_tableView release];
[super dealloc];
}
The testMethod has some code that takes a few seconds to complete. I only have a few clues and I really don't know how and where to start debugging this.
Clue #1: The funniest thing is that if I remove the [_tableView release]; from dealloc then the app doesn't crash. But of course this would cause a leak and I can't remove it.
Clue #2: I've tested this code on a separate "clean" UIViewController with a UITableView and to my surprise it didn't crash.
Clue #3: The app doesn't crash is the UITableView's datasource is set to nil in viewDidLoad.
Clue #4: The app doesn't seem crash if I use the same code in viewDidAppear somewhere else like an IBAction.
Clue #5: I've tried looking over stack data with NSZombie but it gives me tons of data and it leads me nowhere.
I have some very complicated code within my UITableViewDelegate and UITableViewDataSource and I really don't know where to start debugging this. I really hope I don't have to go through line by line or rewrite the entire thing because of this.
Any pointers on where I should be looking?
You're crashing because the controller is still trying to use your tableView reference and since you poped the viewController, everything will go away in the dealloc and the tableView is still populating itself. You can try asking in your dealloc method if your operation is still running, so you can cancel it and the everything should be fine.
The problem is likely that your view controller's last reference is the operation queue holding onto it, which means you are technically calling (or having the system call) some UIKit methods in a background thread (a big no-no) when the operation cleans up.
To prevent this from happening, you need to send a keep-alive message to your controller on the main thread at the end of your operation, by adding something like this to the last line in your
testMethod
:There is still a chance that this message may get processed before the operation queue releases your view controller, but that chance is pretty remote. If it's still happening, you could do something like this:
By sending a message to your view controller on the main thread, it will keep the object alive (NSObject retains your view controller until the main thread handles the message). It will also keep the view controller alive if you perform a selector after a delay.