Movatterモバイル変換


[0]ホーム

URL:


Skip to content
Search Gists
Sign in Sign up

Instantly share code, notes, and snippets.

@steipete
Last activeMay 27, 2024 12:11
    Save steipete/5664345 to your computer and use it in GitHub Desktop.
    This is a guard that tracks down UIKit access on threads other than main. This snippet is taken from the commercial iOS PDF frameworkhttp://pspdfkit.com, but relicensed under MIT. Works because a lot of calls internally call setNeedsDisplay or setNeedsLayout. Won't catch everything, but it's very lightweight and usually does the job.You might n…
    // 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
    Copy link

    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
    Copy link

    @kdogisthebest because that would be hack, this gist is meant to find misuses instead of allowing them :)

    @nightwolf-chen
    Copy link

    This is incredible!

    @jomnius
    Copy link

    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
    Copy link

    dlo commentedSep 21, 2014

    Having the same issue(s) as@jomnius.

    @danydev
    Copy link

    @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
    Copy link

    @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
    Copy link

    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
    Copy link

    Anyone use this gist in Swift project?

    @jeremangnr
    Copy link

    @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
    Copy link

    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
    Copy link

    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
    Copy link

    @Yaro812 Just add the file to your project (and Compile Build Phase). It runs on load:
    attribute((constructor)) static void PSPDFUIKitMainThreadGuard(void)

    @kconnor
    Copy link

    @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
    Copy link

    @ponchorage
    Copy link

    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
    Copy link

    How much can one love a single gist? <3

    @drct
    Copy link

    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
    Copy link

    Is there an OSX version of this?

    @umut-genlik
    Copy link

    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.
    screen shot 2015-11-20 at 2 06 20 pm

    screen shot 2015-11-20 at 2 05 35 pm

    @alexbartisro
    Copy link

    Doesn't work for me. No exception is caught.

    @onmyway133
    Copy link

    @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
    Copy link

    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
    Copy link

    @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.

    @steipete
    Copy link
    Author

    Please dupehttps://openradar.appspot.com/27192338 if you want to see this in UIKit.

    @vedon
    Copy link

    I set the image of UIImageView in another thread ,it can't detect the illegal operation.
    Is there another way to detect it ?

    @messihv5
    Copy link

    it just worked for me, thank you!

    @Schemetrical
    Copy link

    @nrbrook you are literally god

    @freak4pc
    Copy link

    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.
    image

    @HobiSpace
    Copy link

    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

    Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

    [8]ページ先頭

    ©2009-2025 Movatter.jp