巧妙解决 this application is modifying the autolayout engine from a background 问题

有时候我们会遇到这样的错误而导致 app 崩溃:

This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release.

这个问题的解决其实并不复杂。根据错误的描述,是因为我们在代码中在主线程以外的线程中更新了 UI 所导致的,因此,只需要将更新 UI 的代码包裹在 dispatch_async(dispatch_get_main_queue(), ^(void){ <code> }); 块中即可。

问题是 Xcode 的这个错误提示非常之不明确,你在它的调用堆栈根本无法发现错误是由哪句代码导致的。

要调试这个问题,需要用到一些技巧。你可以从这里找到一个 PSPDFUIKitMainThreadGuard.m 文件。它能帮我们找出问题之所在。这是一个在 PSPDFKit 框架中用到的专门查找代码中哪些代码会在辅线程中更新 UI 的工具,但已经修改为支持 MIT 协议了,因此你可以在自己的项目中使用它。它会在背后拦截所有 UIKit 中对 setNeedsDisplay 和 setNeedsLayout 的调用。

上面的源代码需要进行某些地方才能工作。但不要担心,笔者已经替你完成这个工作了:

// Taken from the commercial iOS PDF framework http://pspdfkit.com.
// Copyright (c) 2014 Peter Steinberger, PSPDFKit GmbH. All rights reserved.
// Licensed under MIT (http://opensource.org/licenses/MIT)
//
// You should only use this in debug builds. It doesn't use private API, but I wouldn't ship it.

// PLEASE DUPE rdar://27192338 (https://openradar.appspot.com/27192338) if you would like to see this in UIKit.

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

#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)

// Compile-time selector checks.
#if DEBUG
#define PROPERTY(propName) NSStringFromSelector(@selector(propName))
#else
#define PROPERTY(propName) @#propName
#endif

// http://www.mikeash.com/pyblog/friday-qa-2010-01-29-method-replacement-for-fun-and-profit.html
BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) {
//    NSCParameterAssert(c);
//    NSCParameterAssert(origSEL);
//    NSCParameterAssert(newSEL);
//    NSCParameterAssert(block);

    if ([c instancesRespondToSelector:newSEL]) return YES; // Selector already implemented, skip silently.

    Method origMethod = class_getInstanceMethod(c, origSEL);

    // Add the new method.
    IMP impl = imp_implementationWithBlock(block);
    if (!class_addMethod(c, newSEL, impl, method_getTypeEncoding(origMethod))) {
//        PSPDFLogError(@"Failed to add method: %@ on %@", NSStringFromSelector(newSEL), c);
        return NO;
    }else {
        Method newMethod = class_getInstanceMethod(c, newSEL);

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

SEL _PSPDFPrefixedSelector(SEL selector) {
    return NSSelectorFromString([NSString stringWithFormat:@"pspdf_%@", NSStringFromSelector(selector)]);
}


void PSPDFAssertIfNotMainThread(void) {
    PSPDFAssert(NSThread.isMainThread, @"\nERROR: All calls to UIKit need to happen on the main thread. You have a bug in your code. Use dispatch_async(dispatch_get_main_queue(), ^{ ... }); if you're unsure what thread you're in.\n\nBreak on PSPDFAssertIfNotMainThread to find out where.\n\nStacktrace: %@", NSThread.callStackSymbols);
}

__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) {
                    // Check for window, since *some* UIKit methods are indeed thread safe.
                    // https://developer.apple.com/library/ios/#releasenotes/General/WhatsNewIniPhoneOS/Articles/iPhoneOS4.html
                    /*
                     Drawing to a graphics context in UIKit is now thread-safe. Specifically:
                     The routines used to access and manipulate the graphics context can now correctly handle contexts residing on different threads.
                     String and image drawing is now thread-safe.
                     Using color and font objects in multiple threads is now safe to do.
                     */
                    if (_self.window) PSPDFAssertIfNotMainThread();
                    ((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r);
                });
            }else {
                PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self) {
                    if (_self.window) {
                        if (!NSThread.isMainThread) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
                            dispatch_queue_t queue = dispatch_get_current_queue();
#pragma clang diagnostic pop
                            // iOS 8 layouts the MFMailComposeController in a background thread on an UIKit queue.
                            // https://github.com/PSPDFKit/PSPDFKit/issues/1423
                            if (!queue || !strstr(dispatch_queue_get_label(queue), "UIKit")) {
                                PSPDFAssertIfNotMainThread();
                            }
                        }
                    }
                    ((void ( *)(id, SEL))objc_msgSend)(_self, newSelector);
                });
            }
        }
    }
}

将这个文件的编译选项设置为 -fno-objc-arc。试着编译一下,如果顺利的话,你就可以找出项目中那些错误地在辅线程中更新 UI 的代码了。

运行你的程序,当你有任何类似的错误发生时,Xcode 会在错误发生的代码停下。注意看左边的线程树:

你应该很容易找到你自己的代码,点击它,代码编辑器会跳到断点出现的地方:

这就是你需要去修改的地方。

If you encounter the error message "is not in the sudoers file. This incident will be reported," it means that the user account you are using does not have the necessary permissions to run commands with sudo (superuser do). Sudo is a command in Unix-like operating systems that allows a user with proper permissions to execute commands as a superuser or another user. To resolve this issue, you can try the following steps: 1. Check if your user account has administrative privileges. If not, contact the system administrator or a user with administrative rights to grant you sudo access. 2. If you have administrative privileges, but the error persists, you can try adding your user account to the sudoers file manually. - Open a terminal and run the command `su -` to switch to the root user. - Enter the root password when prompted. - Open the sudoers file using a text editor. In most cases, it is located at `/etc/sudoers`. - Add the following line to the file, replacing `<username>` with your actual username: ``` <username> ALL=(ALL) ALL ``` - Save the file and exit the text editor. - Try running a command with sudo again to see if the issue is resolved. 3. If you are unable to modify the sudoers file or encounter any issues, it is recommended to seek assistance from a system administrator or someone with expertise in managing system permissions. Remember, modifying system files like the sudoers file can have serious consequences if done incorrectly. Proceed with caution and make sure you understand the implications of your actions or seek professional help if unsure.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值