繁体   English   中英

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

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

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

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

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

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

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

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

谢谢你的时间!

我改编了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);
                });
            }
        }
    }
}

它会自动将调用踢入主线程中,因此您甚至不需要执行任何操作,只需将代码放入其中即可。

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

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

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

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

我刚刚用它来拾取我刚收到的一些代码中的大量UIKit /主线程问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM