├── .gitignore ├── LICENSE ├── NBUIKitMainThreadGuard.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Nick Brook 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /NBUIKitMainThreadGuard.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Nick Brook. All rights reserved. 2 | // Inspired by PSPDFUIKitMainThreadGuard.m (https://gist.github.com/steipete/5664345 ) 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 | import UIKit 8 | 9 | #if DEBUG 10 | 11 | // Shim for dispatch_once and DISPATCH_CURRENT_QUEUE_LABEL in swift 3 from http://stackoverflow.com/a/38311178 and https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20160613/002280.html 12 | public extension DispatchQueue { 13 | class var currentLabel: String? { 14 | return String(validatingUTF8: __dispatch_queue_get_label(nil)) 15 | } 16 | } 17 | 18 | extension UIView { 19 | static let classInit : () = { 20 | let swizzle = { (cls: AnyClass, originalSelector: Selector, swizzledSelector: Selector) in 21 | let originalMethod = class_getInstanceMethod(cls, originalSelector) 22 | if let originalMethod = class_getInstanceMethod(cls, originalSelector), let swizzledMethod = class_getInstanceMethod(cls, swizzledSelector) { 23 | 24 | let didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)) 25 | 26 | if didAddMethod { 27 | class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) 28 | } else { 29 | method_exchangeImplementations(originalMethod, swizzledMethod) 30 | } 31 | } 32 | } 33 | for method in ["setNeedsLayout", "setNeedsDisplay", "setNeedsDisplayInRect"] { 34 | swizzle(UIView.self, Selector(method), Selector("nb_\(method)")) 35 | } 36 | }() 37 | 38 | 39 | // MARK: - Method Swizzling 40 | 41 | private func nb_mainThreadCheck() { 42 | // iOS 8 layouts the MFMailComposeController in a background thread on an UIKit queue. 43 | // https://github.com/PSPDFKit/PSPDFKit/issues/1423 44 | if !Thread.isMainThread && DispatchQueue.currentLabel?.hasPrefix("UIKit") != true { 45 | let stack = Thread.callStackSymbols.joined(separator: "\n") 46 | assert(false, "\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 nb_mainThreadCheck to find out where.\n\nStacktrace:\n\(stack)") 47 | } 48 | } 49 | 50 | func nb_setNeedsLayout() { 51 | self.nb_mainThreadCheck() 52 | self.nb_setNeedsLayout() 53 | } 54 | 55 | func nb_setNeedsDisplay() { 56 | self.nb_mainThreadCheck() 57 | self.nb_setNeedsDisplay() 58 | } 59 | 60 | func nb_setNeedsDisplayInRect(rect: CGRect) { 61 | self.nb_mainThreadCheck() 62 | self.nb_setNeedsDisplayInRect(rect: rect) 63 | } 64 | } 65 | #else // DEBUG 66 | extension UIView { 67 | static let classInit : () = {}() 68 | } 69 | #endif 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NBUIKitMainThreadGuard 2 | Checks UIView methods are being called on the correct thread. Swift re-work of [PSPDFUIKitMainThreadGuard.m](https://gist.github.com/steipete/5664345). 3 | 4 | ## Installation 5 | 1. Drag into project 6 | 1. Select your project in the navigator, then the build settings tab 7 | 1. Filter with 'preprocessor macro', then expand the row with the arrow on the left 8 | 1. Double click in the debug row value, type `DEBUG=1` 9 | 1. Filter with 'other swift flags', expland, in the debug row type `-DDEBUG` 10 | 1. Add the following to the top of your App Delegate: 11 | 12 | ``` Swift 13 | override init() { 14 | super.init() 15 | UIView.classInit 16 | } 17 | ``` 18 | 19 | ## License 20 | MIT 21 | --------------------------------------------------------------------------------