Instantly share code, notes, and snippets.
- Star(1,006)
You must be signed in to star a gist - Fork(134)
You must be signed in to fork a gist
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 | |
#definePROPERTY(propName)NSStringFromSelector(@selector(propName)) | |
#else | |
#definePROPERTY(propName) @#propName | |
#endif | |
// http://www.mikeash.com/pyblog/friday-qa-2010-01-29-method-replacement-for-fun-and-profit.html | |
BOOLPSPDFReplaceMethodWithBlock(Class c,SEL origSEL,SEL newSEL,id block) { | |
NSCParameterAssert(c); | |
NSCParameterAssert(origSEL); | |
NSCParameterAssert(newSEL); | |
NSCParameterAssert(block); | |
if ([cinstancesRespondToSelector:newSEL])returnYES;// 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); | |
returnNO; | |
}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); | |
} | |
} | |
returnYES; | |
} | |
SEL_PSPDFPrefixedSelector(SEL selector) { | |
returnNSSelectorFromString([NSStringstringWithFormat:@"pspdf_%@",NSStringFromSelector(selector)]); | |
} | |
#definePSPDFAssert(expression, ...) \ | |
do {if(!(expression)) { \ | |
NSLog(@"%@", [NSStringstringWithFormat:@"Assertion failure:%s in%s on line%s:%d.%@", #expression, __PRETTY_FUNCTION__, __FILE__, __LINE__, [NSStringstringWithFormat:@"" __VA_ARGS__]]); \ | |
abort(); }}while(0) | |
voidPSPDFAssertIfNotMainThread(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([NSStringstringWithFormat:@"pspdf_%@", selStr]); | |
if ([selStrhasSuffix:@":"]) { | |
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); | |
}); | |
} | |
} | |
} | |
} |
k3zi commentedJul 11, 2014
Why don't we make a swizzle that automatically kicks every call to a UIKit object into the main thread???
Like this:https://gist.github.com/kdogisthebest/98ca835b15077d11dafc
krzysztofzablocki commentedJul 22, 2014
@kdogisthebest because that would be hack, this gist is meant to find misuses instead of allowing them :)
nightwolf-chen commentedSep 2, 2014
This is incredible!
jomnius commentedSep 17, 2014
Line 30: Implicit declaration of function "PSPDFAssert' is invalid in C99
Line 38: Implicit declaration of function "PSPDFLogError' is invalid in C99
First one can be fixed by moving PSPDFAssert definition higher in code, but the other one is a mystery. Guess it can be replaced by NSLog (or similar)? I put there PSPDFAssert for now.
dlo commentedSep 21, 2014
Having the same issue(s) as@jomnius.
danydev commentedOct 11, 2014
@dlo you just need to use NSLog instead of PSPDFLogError and move PSPDFAssert on line 16. I just created a fork that should compile right away with a few minor changes.
https://gist.github.com/danydev/9cb3539198c6af3446ed
onmyway133 commentedFeb 2, 2015
@andrewebling I think we should do compile time check around the constructor
@steipete How to avoid checking certain scenarios ? Like thishttp://stackoverflow.com/questions/25963367/mpvolumeview-initwithframestyle-not-called-on-main-thread-when-loading-uiweb
vandadschibsted commentedApr 24, 2015
Thanks for this. Even though I didn't find anything, but it's good to know this exists...
I just did a test with this code:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{ UIButton *btn = [UIButtonbuttonWithType:UIButtonTypeSystem]; btn.tag =1; [self.viewaddSubview:btn]; });
and this gist could not find this simple UI work on a non UI thread... what could be wrong?
jerry-sl commentedApr 28, 2015
Anyone use this gist in Swift project?
jeremangnr commentedMay 13, 2015
@vandadschibsted I was having the same issue and came up with a simple fix. I removed theif (_self.window)
checks from the swizzled methods, that did the trick for me... thanks to this I found the cause of an issue that was disabling all animations across the app!
RameshAran commentedSep 5, 2015
Thank you@jeremangnr.
After commenting the following condition check "_self.window", its working. Its capturing the inconsistencies like the following
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//UI updation
});
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
//UI updation
})];
I am getting the following Assertion failure.
Assertion failure: NSThread.isMainThread in void PSPDFAssertIfNotMainThread() on line /Users/<account_name>/Documents/Tutorials/TestSample/PSPDFUIKitMainThreadGuard.m:146.
Thanks you@steipete for sharing this. Its really useful.
Yaro812 commentedSep 25, 2015
How to properly add this code to the project?
Is addingPSPDFUIKitMainThreadGuard();
to+ (void)initialize
of myAppDelegate
is a proper way to use this code?
kconnor commentedSep 26, 2015
@Yaro812 Just add the file to your project (and Compile Build Phase). It runs on load:
attribute((constructor)) static void PSPDFUIKitMainThreadGuard(void)
kconnor commentedSep 26, 2015
@steipete My osx version of this gist is unhappy on El Capitan. Looks like it does more UI work off the main thread. It's asserting where it didn't before. Thanks very much for making it public.
nrbrook commentedOct 11, 2015
I made aswift version
ponchorage commentedOct 27, 2015
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.
jdarowski commentedOct 27, 2015
How much can one love a single gist? <3
drct commentedNov 12, 2015
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.
fbartho commentedNov 14, 2015
Is there an OSX version of this?
umut-genlik commentedNov 20, 2015
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 toBuild Phases -> Compile Sources
and find thePSPDFUIKitMainThreadGuard.m
double click it add the non-arc compiling flag.-fno-objc-arc
That is it.
alexbartisro commentedNov 27, 2015
Doesn't work for me. No exception is caught.
onmyway133 commentedJan 16, 2016
@jerry-sl You can use swizzle on UIView in Swift, here is an examplehttps://github.com/onmyway133/MainThreadGuard
You can use DEBUG custom flaghttp://stackoverflow.com/questions/24003291/ifdef-replacement-in-swift-language or useassert
sahabe1 commentedMay 3, 2016
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)
panliming-tuya commentedJun 7, 2016
@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 dupehttps://openradar.appspot.com/27192338 if you want to see this in UIKit.
vedon commentedJul 17, 2016
I set the image of UIImageView in another thread ,it can't detect the illegal operation.
Is there another way to detect it ?
messihv5 commentedOct 23, 2016
it just worked for me, thank you!
Schemetrical commentedNov 12, 2016
@nrbrook you are literally god
freak4pc commentedMar 29, 2017
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.
HobiSpace commentedJan 9, 2019
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