如何从辅助线程中找到对UIKit实例的调用?

时间:2011-10-20 16:38:47

标签: objective-c ios cocoa-touch xcode4 ios5

我的应用程序在iOS 5中崩溃,因为我有一些代码从辅助线程调用UIKit实例。当您看到以下错误时,您知道有这个问题:

bool _WebTryThreadLock(bool),0x811bf20:不允许在网络线程上进行多次锁定!请提交一个错误。现在崩溃......

所以我的问题是我可以通过哪些方法找到从辅助线程调用UIKit实例的代码?

以下是我已经尝试过的一些事情:

  1. 注释掉可能违反规则的块
  2. 在可能在辅助线程中处理的位置添加了assert([NSThread isMainThread])
  3. _WebTryThreadLock
  4. 添加了符号断点

    这些东西帮助我找到问题所在。但是,在我的最终崩溃中,_WebTryThreadLock断点在任何其他线程中都没有堆栈跟踪。那么,如何在没有堆栈跟踪的情况下找到导致问题的代码呢?

    谢谢你的时间!

4 个答案:

答案 0 :(得分:3)

您的assert()可能是最有价值的工具。我已经知道在Controller类的每个方法的开头都有类似的断言。如果找不到,我将断言添加到我的View类中。如果没有找到它,我将它添加到我认为只是主线程的任何Model类中。

对于@ craig的评论,它声称是内部错误的事实可能是准确的。但我认为你是在正确的道路上仔细检查你自己的代码。

答案 1 :(得分:3)

我改编了PSPDFUIKitMainThreadGuard.m,让人不用担心这些事情。在这里: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);
                });
            }
        }
    }
}

它会自动将调用踢入主线程中,因此除了将代码放入其中之外,您甚至不必做任何事情。

答案 2 :(得分:1)

出现此问题的原因是您希望以某种方式从辅助线程访问UI,它可以从其他任何内容的webview访问。这是不允许的,因为UIKit不是线程安全的,只能从MainThread访问。 您可以做的第一件事就是将线程调用更改为[self performSelectorOnMainThread:@selector(myMethod) withObject:nil waitUntilDone:NO];(查找文档)。 如果您没有其他选择,可以使用GCD(Grand Central Dispathc)...

答案 3 :(得分:1)

此代码(仅添加到项目并在没有ARC的情况下编译此文件)会导致主线程外的UIKit访问断言:https://gist.github.com/steipete/5664345

我刚刚使用它来解决我刚接到的一些代码中的大量UIKit /主线程问题。