└── PSPDFUIKitMainThreadGuard.m /PSPDFUIKitMainThreadGuard.m: -------------------------------------------------------------------------------- 1 | // Taken from the commercial iOS PDF framework http://pspdfkit.com. 2 | // Copyright (c) 2014 Peter Steinberger, PSPDFKit GmbH. All rights reserved. 3 | // Licensed under MIT (http://opensource.org/licenses/MIT) 4 | // 5 | // You should only use this in debug builds. It doesn't use private API, but I wouldn't ship it. 6 | 7 | #if DEBUG 8 | 9 | @import Foundation; 10 | @import UIKit; 11 | #import 12 | #import 13 | 14 | #define PROPERTY(propName) NSStringFromSelector(@selector(propName)) 15 | 16 | #define PSPDFAssert(expression, ...) \ 17 | do { if(!(expression)) { \ 18 | NSLog(@"%@", [NSString stringWithFormat: @"Assertion failure: %s in %s on line %s:%d. %@", #expression, __PRETTY_FUNCTION__, __FILE__, __LINE__, [NSString stringWithFormat:@"" __VA_ARGS__]]); \ 19 | abort(); }} while(0) 20 | 21 | // http://www.mikeash.com/pyblog/friday-qa-2010-01-29-method-replacement-for-fun-and-profit.html 22 | BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) { 23 | PSPDFAssert(c && origSEL && newSEL && block); 24 | if ([c instancesRespondToSelector:newSEL]) return YES; // Selector already implemented, skip silently. 25 | 26 | Method origMethod = class_getInstanceMethod(c, origSEL); 27 | 28 | // Add the new method. 29 | IMP impl = imp_implementationWithBlock(block); 30 | if (!class_addMethod(c, newSEL, impl, method_getTypeEncoding(origMethod))) { 31 | NSLog(@"Failed to add method: %@ on %@", NSStringFromSelector(newSEL), c); 32 | return NO; 33 | }else { 34 | Method newMethod = class_getInstanceMethod(c, newSEL); 35 | 36 | // If original doesn't implement the method we want to swizzle, create it. 37 | if (class_addMethod(c, origSEL, method_getImplementation(newMethod), method_getTypeEncoding(origMethod))) { 38 | class_replaceMethod(c, newSEL, method_getImplementation(origMethod), method_getTypeEncoding(newMethod)); 39 | }else { 40 | method_exchangeImplementations(origMethod, newMethod); 41 | } 42 | } 43 | return YES; 44 | } 45 | 46 | SEL _PSPDFPrefixedSelector(SEL selector) { 47 | return NSSelectorFromString([NSString stringWithFormat:@"pspdf_%@", NSStringFromSelector(selector)]); 48 | } 49 | 50 | void PSPDFAssertIfNotMainThread(void) { 51 | 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); 52 | } 53 | 54 | __attribute__((constructor)) static void PSPDFUIKitMainThreadGuard(void) { 55 | @autoreleasepool { 56 | for (NSString *selStr in @[PROPERTY(setNeedsLayout), PROPERTY(setNeedsDisplay), PROPERTY(setNeedsDisplayInRect:)]) { 57 | SEL selector = NSSelectorFromString(selStr); 58 | SEL newSelector = NSSelectorFromString([NSString stringWithFormat:@"pspdf_%@", selStr]); 59 | if ([selStr hasSuffix:@":"]) { 60 | PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self, CGRect r) { 61 | // Check for window, since *some* UIKit methods are indeed thread safe. 62 | // https://developer.apple.com/library/ios/#releasenotes/General/WhatsNewIniPhoneOS/Articles/iPhoneOS4.html 63 | /* 64 | Drawing to a graphics context in UIKit is now thread-safe. Specifically: 65 | 66 | The routines used to access and manipulate the graphics context can now correctly handle contexts residing on different threads. 67 | 68 | String and image drawing is now thread-safe. 69 | 70 | Using color and font objects in multiple threads is now safe to do. 71 | */ 72 | if (_self.window) PSPDFAssertIfNotMainThread(); 73 | ((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r); 74 | }); 75 | }else { 76 | PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self) { 77 | if (_self.window) { 78 | if (!NSThread.isMainThread) { 79 | #pragma clang diagnostic push 80 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 81 | dispatch_queue_t queue = dispatch_get_current_queue(); 82 | #pragma clang diagnostic pop 83 | // iOS 8 layouts the MFMailComposeController in a background thread on an UIKit queue. 84 | // https://github.com/PSPDFKit/PSPDFKit/issues/1423 85 | if (!queue || !strstr(dispatch_queue_get_label(queue), "UIKit")) { 86 | PSPDFAssertIfNotMainThread(); 87 | } 88 | } 89 | } 90 | ((void ( *)(id, SEL))objc_msgSend)(_self, newSelector); 91 | }); 92 | } 93 | } 94 | } 95 | } 96 | 97 | #endif --------------------------------------------------------------------------------