【问题标题】:How can I find calls to UIKit instances from a secondary thread?如何从辅助线程中找到对 UIKit 实例的调用?
【发布时间】:2011-12-11 23:01:16
【问题描述】:

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

bool _WebTryThreadLock(bool), 0x811bf20: web 线程上的多个锁不允许!请提交一个错误。现在崩溃了……

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

以下是我已经尝试过的一些方法:

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

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

感谢您的宝贵时间!

【问题讨论】:

  • 这可能是一个模拟器错误。在实际的 iOS 设备上运行时是否也会出现此问题?
  • 是的,它发生在 iOS 5 模拟器和运行 iOS 5 的 iOS 设备中。
  • 啊,好吧。对该错误消息的快速谷歌搜索让我相信这是一个模拟器错误,但如果它发生在其他地方,那么我不完全确定。您是否有 2 个以上的 UIWebViews 同时显示在屏幕上/正在加载?
  • @johnnieb 你有没有发现这个问题?在我的应用程序中,我遇到了同样的错误。在我快速关闭 2 个模态视图后,下次我尝试呈现一个新的模态视图时它会崩溃。我试图改变周围的东西,但我一直在崩溃。
  • @RyanGarchinsky 我们确实找到了问题所在。这根本不是我们所期望的。我们有一个登录屏幕,可以自动将用户登录到应用程序中。它通过设置 firstResponder 来呈现一个键盘。 UIKit 保留了键盘 firstResponder 引用,这反过来又阻塞了我们的 WebThread。我们花了很多时间试图追踪这个。祝你好运!

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


【解决方案1】:

您的assert() 可能是这方面最有价值的工具。众所周知,我在我的 Controller 类中的每个方法的开头都放置了一个类似的断言。如果找不到,我将断言添加到我的视图类中。如果没有找到它,我将它添加到我认为只是主线程的任何模型类中。

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      我刚刚用它在我刚刚拾取的一些代码中拾取了许多 UIKit/主线程问题。

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

        • 你的要点不再存在
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-22
        • 2022-01-05
        • 2011-09-16
        • 1970-01-01
        相关资源
        最近更新 更多