-
-
Save steipete/5664345 to your computer and use it in GitHub Desktop.
// 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> | |
// 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)]); | |
} | |
#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) | |
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); | |
}); | |
} | |
} | |
} | |
} |
Still not clear to me how to actually use this. The file is in my project and it appears in the Compile Build Phase. Do I need to import the file in the AppDelegate or somewhere and is there a call I need to make? I ask because it's not catching any problems but the new version of Xcode 7.1 seems to think there are issues. It's giving me a stack trace saying that I'm modifying the autolayout engine from a background thread.
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.
How much can one love a single gist? <3
Minor improvements: Moved "#define PSPDFAssert(expression, ...) ...", commented "PSPDFLogError ..." out and added "#import <UIKit/UIKit.h>". If interested, you can pull from here: https://gist.github.com/drct/bb4b1f93f8790cca9c9f (since no pull requests are possible with gists.
Is there an OSX version of this?
How to use it:
@ponchorage just add the .m file (the one @drct enhanced) to your project, then after it is added to your project go to Build Phases -> Compile Sources
and find the PSPDFUIKitMainThreadGuard.m
double click it add the non-arc compiling flag. -fno-objc-arc
That is it.
Doesn't work for me. No exception is caught.
@jerry-sl You can use swizzle on UIView in Swift, here is an example https://github.com/onmyway133/MainThreadGuard
You can use DEBUG custom flag http://stackoverflow.com/questions/24003291/ifdef-replacement-in-swift-language or use assert
How to use PSPDFUIKitMainThreadGuard.m . I am getting error
Undefined symbols for architecture armv7:
"_PSPDFAssert", referenced from:
_PSPDFReplaceMethodWithBlock in PSPDFUIKitMainThreadGuard.o
(maybe you meant: _PSPDFAssertIfNotMainThread)
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)
@sahabe1 same as me, but look this
drct commented on 12 Nov 2015
Minor improvements: Moved "#define PSPDFAssert(expression, ...) ...", commented "PSPDFLogError ..." out and added "#import ". If interested, you can pull from here: https://gist.github.com/drct/bb4b1f93f8790cca9c9f (since no pull requests are possible with gists.
Please dupe https://openradar.appspot.com/27192338 if you want to see this in UIKit.
I set the image of UIImageView in another thread ,it can't detect the illegal operation.
Is there another way to detect it ?
it just worked for me, thank you!
@nrbrook you are literally god
FYI the Swift port by @onmyway133 will probably be broken soon. Any other thoughts on how to get this done? This is a great saver when sometimes forgetting to go back to the UI Thread.
Stacktrace: (
0 MYXJ 0x000000010238faec PSPDFAssertIfNotMainThread + 84
1 MYXJ 0x000000010238fc10 __PSPDFUIKitMainThreadGuard_block_invoke_2 + 36
2 UIKitCore 0x00000001e470ccc4 + 336
3 UIKitCore 0x00000001e4738548 + 1644
4 libobjc.A.dylib 0x00000001b5f1b604 + 68
5 QuartzCore 0x00000001bb3b1f94 + 188
6 QuartzCore 0x00000001bb3b2274 + 328
7 QuartzCore 0x00000001bb316000 + 332
8 QuartzCore 0x00000001bb345518 + 624
9 QuartzCore 0x00000001bb346358 + 96
10 CoreFoundation 0x00000001b6cb3fe0 + 36
11 CoreFoundation 0x00000001b6caeab8 + 408
12 CoreFoundation 0x00000001b6caf03c + 1248
13 CoreFoundation 0x00000001b6cae844 CFRunLoopRunSpecific + 452
14 WebCore 0x00000001bfcc70e0 + 572
15 libsystem_pthread.dylib 0x00000001b6929a04 + 132
16 libsystem_pthread.dylib 0x00000001b6929960 _pthread_start + 52
17 libsystem_pthread.dylib 0x00000001b6931df4 thread_start + 4
)
Triggers on WebThread
I made a swift version