美文网首页Full Stack Developer
iOS 查找代码里的crashes

iOS 查找代码里的crashes

作者: samtake | 来源:发表于2019-03-08 21:00 被阅读2次

    iOS中在非主线程更新UI会有crashes闪退提示

    UI在主线程之外更新了的cash.png

    解决办法就是将产生crashes的代码放到主线程执行就可以了,但该代码块有时候不能很直观的看出来,本篇记录下如何便捷的找出crashes代码块。

    往工程里添加PSPDFUIKitMainThreadGuard.m文件,完整代码如下👇

    // 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);
                    });
                }
            }
        }
    }
    
    

    build一下,这时在左边的线程树,可以找到你自己编写的类文件名,点击该类文件名跳转到对应的crashes代码块,然后将该代码添加到主线程执行就可以了


    crashes显示信息.png WechatIMG8.png

    本来我是想打dispatch_async,但代码提示却打出了dispatch_main_async_safe

    
    dispatch_async(dispatch_get_main_queue(), ^{
               [self.tableView reloadData];
            });
    
    
    
    dispatch_main_async_safe(^{
            [self.tableView reloadData];
        });
    

    刚看dispatch_main_async_safe感觉挺陌生的,点击进去发觉原来是SDWebImage定义的一个主线程安全,源码如下

    #ifndef dispatch_main_async_safe
    #define dispatch_main_async_safe(block)\
        if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
            block();\
        } else {\
            dispatch_async(dispatch_get_main_queue(), block);\
        }
    #endif
    

    SDWebImage 上关于 dispatch_main_async_safe 的链接

    🔚

    相关文章

      网友评论

        本文标题:iOS 查找代码里的crashes

        本文链接:https://www.haomeiwen.com/subject/rsvdpqtx.html