views:

1209

answers:

5

I have a UIWebView in a viewcontroller, which has two methods as below. The question is if I pop out(tap back on navigation bar) this controller before the second thread is done, the app will crash after [super dealloc], because "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.". Any help would be really appreciated.

-(void)viewDidAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(load) object:nil];
    [operationQueue addOperation:operation];
    [operation release];
}

-(void)load {
    [NSThread sleepForTimeInterval:5];
    [self performSelectorOnMainThread:@selector(done) withObject:nil waitUntilDone:NO];
}
A: 

In general, you should cancel any background operations when the view that uses them is going away. As in:

- (void)viewWillDisappear:(BOOL)animated {
  [operationQueue cancelAllOperations];
  [super viewWillDisappear:animated;
}
Denis Hennessy
Thank you for replying. But I've added that and it seems that dealloc is always called in the secondary thread. Still can't figure out the reason.
Tao
A: 

Hey Tao,

I'm not sure exactly what's going based on your code, but it looks like viewDidAppear is getting called and creating the second thread, and then you're navigating away from the controller and releasing it, and then the second thread finishes and calls performSelectorOnMainThread on the released object "self". I think you may just need to check that the release has not occurred?

The error message you're getting implies that you're running some UIKit code from your second thread. Apple recently added some checks for threaded calls to UIKit, and I think you probably just need to refactor your load function to update the UI on the main thread, instead of calling UIWebView functions from the second thread.

Hope that helps!

Ben Gotow
+1  A: 

I currently have a similar problem in my application. A view controller which displays a UIWebView is pushed to a navigation controller and starts a background thread to retrieve data. If you hit the back button before the thread has finished, the application crashes with the same error message.

The problem seems to be that NSThread retains the target (self) and object (argument) and releases it after the method has been run -- unfortunately it releases both from within the thread. So when the controller gets created, the retain count is 1, when the thread is started, the controller gets a retain count of 2. When you pop the controller before the thread is done, the navigation controller releases the controller, which results in a retain count of 1. So far this is fine -- But if the thread finally finishes, NSThread releases the controller, which results in a retain count of 0 and an immediate dealloc from within the thread. This makes the UIWebView (which is released in the dealloc method of the controller) raise that thread warning exception and crash.

I successfully worked around this by using [[self retain] autorelease] as the last statement in the thread (right before the thread releases its pool). This ensures that the controller object is not released immediately but marked as autoreleased and deallocated later in the main thread's run loop. However this is a somewhat dirty hack and I'd rather prefer to find a better solution.

Andreas
Your 'work around' solution works for me, thanks.BTW, Tao, what is your last decision on which method that you use?
SimpleCode
A: 

I tried both solutions posted above, [operationQueue cancelAllOperations], and [[self retain] autorelease]. However, with quick clicking, there are still cases where the retain count falls to 0 and the class gets deallocated on the secondary thread. In order to avoid a crash, I put the following in my dealloc for now:

    if ([NSThread isMainThread]) {
        [super dealloc];
    }

which is an obvious leak, but seems to be the lesser of 2 evils.

Any additional insight from anyone encountering this problem is welcome.

Patrick O'Shaughnessey
A: 

I tried :

[self retain];
[self performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];

which seems to work even better.

thomas