Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I find calls to UIKit instances from a secondary thread?

My app is crashing in iOS 5 because I have some code that is calling UIKit instances from a secondary thread. You know you have this problem when you see the following error:

bool _WebTryThreadLock(bool), 0x811bf20: Multiple locks on web thread not allowed! Please file a bug. Crashing now…

So my question is what are some ways that I can find the code that is calling the UIKit instances from a secondary thread?

Here are some things I’ve tried already:

  1. Commented out blocks that could be violating the rule
  2. Added assert([NSThread isMainThread]) in places that might be processing in secondary thread
  3. Added a symbolic breakpoint for _WebTryThreadLock

These things have helped me to find problem areas. However, in my final crash the _WebTryThreadLock breakpoint has no stack trace in any of the other threads. So, how I can find the code that causing the problem without a stack trace?

Thanks for your time!

like image 373
johnnieb Avatar asked Oct 20 '11 16:10

johnnieb


4 Answers

Your assert() is probably the most valuable tool in this. I've been known to put a similar assertion at the beginning of every method in my Controller classes. If that doesn't find it, I add the assertion to my View classes. If that doesn't find it, I add it to any Model classes that I think are main-thread only.

To @craig's comment, the fact that it claims to be an internal bug might be accurate. But I think you're on the right path to closely examine your own code first.

like image 187
Rob Napier Avatar answered Oct 20 '22 09:10

Rob Napier


I adapted the PSPDFUIKitMainThreadGuard.m to allow one to not have to worry about these things. Here: https://gist.github.com/k3zi/98ca835b15077d11dafc :

#import <objc/runtime.h>
#import <objc/message.h>

// Compile-time selector checks.

#define PROPERTY(propName) NSStringFromSelector(@selector(propName))

// A better assert. NSAssert is too runtime dependant, and assert() doesn't log.
// http://www.mikeash.com/pyblog/friday-qa-2013-05-03-proper-use-of-asserts.html
// Accepts both:
// - PSPDFAssert(x > 0);
// - PSPDFAssert(y > 3, @"Bad value for y");
#define PSPDFAssert(expression, ...) \
do { if(!(expression)) { \
NSLog(@"%@", [NSString stringWithFormat: @"Assertion failure: %s in %s on line %s:%d. %@", #expression, __PRETTY_FUNCTION__, __FILE__, __LINE__, [NSString stringWithFormat:@"" __VA_ARGS__]]); \
abort(); }} while(0)

///////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - Helper for Swizzling

BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) {
    PSPDFAssert(c && origSEL && newSEL && block);
    Method origMethod = class_getInstanceMethod(c, origSEL);
    const char *encoding = method_getTypeEncoding(origMethod);

    // Add the new method.
    IMP impl = imp_implementationWithBlock(block);
    if (!class_addMethod(c, newSEL, impl, encoding)) {
        NSLog(@"Failed to add method: %@ on %@", NSStringFromSelector(newSEL), c);
        return NO;
    }else {
        // Ensure the new selector has the same parameters as the existing selector.
        Method newMethod = class_getInstanceMethod(c, newSEL);
        PSPDFAssert(strcmp(method_getTypeEncoding(origMethod), method_getTypeEncoding(newMethod)) == 0, @"Encoding must be the same.");

        // If original doesn't implement the method we want to swizzle, create it.
        if (class_addMethod(c, origSEL, method_getImplementation(newMethod), encoding)) {
            class_replaceMethod(c, newSEL, method_getImplementation(origMethod), encoding);
        }else {
            method_exchangeImplementations(origMethod, newMethod);
        }
    }
    return YES;
}

// This installs a small guard that checks for the most common threading-errors in UIKit.
// This won't really slow down performance but still only is compiled in DEBUG versions of PSPDFKit.
// @note No private API is used here.
__attribute__((constructor)) static void PSPDFUIKitMainThreadGuard(void) {
    @autoreleasepool {
        for (NSString *selStr in @[PROPERTY(setNeedsLayout), PROPERTY(setNeedsDisplay), PROPERTY(setNeedsDisplayInRect:)]) {
            SEL selector = NSSelectorFromString(selStr);
            SEL newSelector = NSSelectorFromString([NSString stringWithFormat:@"pspdf_%@", selStr]);
            if ([selStr hasSuffix:@":"]) {
                PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self, CGRect r) {
                    if(!NSThread.isMainThread){
                        dispatch_async(dispatch_get_main_queue(), ^{
                            ((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r);
                        });
                    }else{
                        ((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r);
                    }
                });
            }else {
                PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self) {
                    if(!NSThread.isMainThread){
                        dispatch_async(dispatch_get_main_queue(), ^{
                            ((void ( *)(id, SEL))objc_msgSend)(_self, newSelector);
                        });
                    }else
                        ((void ( *)(id, SEL))objc_msgSend)(_self, newSelector);
                });
            }
        }
    }
}

It automatically kicks calls into the main thread and thus you wouldn't even have to do anything but plop the code in.

like image 38
keji Avatar answered Oct 20 '22 09:10

keji


This problem comes because you want to access to UI from secondary thread somehow, it can from webview of whatever else. It is not permitted because UIKit is not thread safe and can be accessed only from MainThread. The very first thing you can do is to change your thread call to [self performSelectorOnMainThread:@selector(myMethod) withObject:nil waitUntilDone:NO]; (look for documentation). In case when you have no other choice you can use GCD(Grand Central Dispathc)...

like image 32
Garnik Avatar answered Oct 20 '22 09:10

Garnik


This code (just add to project and compile this file without ARC) causes assertions on UIKit access outside the main thread: https://gist.github.com/steipete/5664345

I've just used it to pickup numerous UIKit/main thread issues in some code I've just picked up.

like image 37
Andrew Ebling Avatar answered Oct 20 '22 09:10

Andrew Ebling