Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save maicoder/146fba1b966abf180bac21ce2325574c to your computer and use it in GitHub Desktop.
Save maicoder/146fba1b966abf180bac21ce2325574c to your computer and use it in GitHub Desktop.

Revisions

  1. @steipete steipete revised this gist Jul 6, 2016. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -4,6 +4,8 @@
    //
    // 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>

  2. @steipete steipete revised this gist Jun 10, 2016. 1 changed file with 5 additions and 1 deletion.
    6 changes: 5 additions & 1 deletion PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -16,7 +16,11 @@

    // 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) {
    PSPDFAssert(c && origSEL && newSEL && 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);
  3. @steipete steipete revised this gist Sep 16, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    // Taken from the commercial iOS PDF framework http://pspdfkit.com.
    // Copyright (c) 2013 Peter Steinberger. All rights reserved.
    // 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.
  4. @steipete steipete revised this gist Sep 16, 2014. 1 changed file with 38 additions and 0 deletions.
    38 changes: 38 additions & 0 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -14,6 +14,44 @@
    #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) {
    PSPDFAssert(c && origSEL && newSEL && 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:)]) {
  5. @steipete steipete revised this gist Sep 16, 2014. 1 changed file with 26 additions and 51 deletions.
    77 changes: 26 additions & 51 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -14,70 +14,45 @@
    #define PROPERTY(propName) @#propName
    #endif

    // A better assert. NSAssert is too runtime dependant, and assert() doesn't log.
    // http://www.mikeash.com/pyblog/friday-qa-2013-05-03-proper-use-of-asserts.html
    // Accepts both:
    // - PSPDFAssert(x > 0);
    // - PSPDFAssert(y > 3, @"Bad value for y");
    #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)

    ///////////////////////////////////////////////////////////////////////////////////////////
    #pragma mark - Helper for Swizzling

    BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) {
    PSPDFAssert(c && origSEL && newSEL && block);
    Method origMethod = class_getInstanceMethod(c, origSEL);
    const char *encoding = method_getTypeEncoding(origMethod);

    // Add the new method.
    IMP impl = imp_implementationWithBlock(block);
    if (!class_addMethod(c, newSEL, impl, encoding)) {
    NSLog(@"Failed to add method: %@ on %@", NSStringFromSelector(newSEL), c);
    return NO;
    }else {
    // Ensure the new selector has the same parameters as the existing selector.
    Method newMethod = class_getInstanceMethod(c, newSEL);
    PSPDFAssert(strcmp(method_getTypeEncoding(origMethod), method_getTypeEncoding(newMethod)) == 0, @"Encoding must be the same.");

    // If original doesn't implement the method we want to swizzle, create it.
    if (class_addMethod(c, origSEL, method_getImplementation(newMethod), encoding)) {
    class_replaceMethod(c, newSEL, method_getImplementation(origMethod), encoding);
    }else {
    method_exchangeImplementations(origMethod, newMethod);
    }
    }
    return YES;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////
    #pragma mark - Tracks down calls to UIKit from a Thread other than Main

    static 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]);
    }

    // This installs a small guard that checks for the most common threading-errors in UIKit.
    // This won't really slow down performance but still only is compiled in DEBUG versions of PSPDFKit.
    // @note No private API is used here.
    __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) {
    PSPDFAssertIfNotMainThread();
    // 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) {
    PSPDFAssertIfNotMainThread();
    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);
    });
    }
    }
    }
    }
    }
  6. @steipete steipete revised this gist Jun 2, 2013. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,6 @@

    #import <objc/runtime.h>
    #import <objc/message.h>
    #import <QuartzCore/QuartzCore.h>

    // Compile-time selector checks.
    #if DEBUG
  7. @steipete steipete revised this gist Jun 2, 2013. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -8,6 +8,7 @@
    #import <objc/message.h>
    #import <QuartzCore/QuartzCore.h>

    // Compile-time selector checks.
    #if DEBUG
    #define PROPERTY(propName) NSStringFromSelector(@selector(propName))
    #else
  8. @steipete steipete revised this gist Jun 2, 2013. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -24,6 +24,9 @@
    NSLog(@"%@", [NSString stringWithFormat: @"Assertion failure: %s in %s on line %s:%d. %@", #expression, __PRETTY_FUNCTION__, __FILE__, __LINE__, [NSString stringWithFormat:@"" __VA_ARGS__]]); \
    abort(); }} while(0)

    ///////////////////////////////////////////////////////////////////////////////////////////
    #pragma mark - Helper for Swizzling

    BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) {
    PSPDFAssert(c && origSEL && newSEL && block);
    Method origMethod = class_getInstanceMethod(c, origSEL);
  9. @steipete steipete revised this gist Jun 2, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // Taken from the commercial iOS PDF framework http://pspdfkit.com.
    // Copyright (c) 2013 Peter Steinberger. All rights reserved.
    // Licensed under MIT.
    // 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.

  10. @steipete steipete revised this gist Jun 2, 2013. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -25,7 +25,7 @@
    abort(); }} while(0)

    BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) {
    NSCParameterAssert(c && origSEL && newSEL && block);
    PSPDFAssert(c && origSEL && newSEL && block);
    Method origMethod = class_getInstanceMethod(c, origSEL);
    const char *encoding = method_getTypeEncoding(origMethod);

    @@ -37,7 +37,7 @@ BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) {
    }else {
    // Ensure the new selector has the same parameters as the existing selector.
    Method newMethod = class_getInstanceMethod(c, newSEL);
    NSCAssert(strcmp(method_getTypeEncoding(origMethod), method_getTypeEncoding(newMethod)) == 0, @"Encoding must be the same.");
    PSPDFAssert(strcmp(method_getTypeEncoding(origMethod), method_getTypeEncoding(newMethod)) == 0, @"Encoding must be the same.");

    // If original doesn't implement the method we want to swizzle, create it.
    if (class_addMethod(c, origSEL, method_getImplementation(newMethod), encoding)) {
  11. @steipete steipete revised this gist Jun 2, 2013. 1 changed file with 7 additions and 6 deletions.
    13 changes: 7 additions & 6 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -1,12 +1,18 @@
    // Taken from the commercial iOS PDF framework http://pspdfkit.com.
    // Copyright (c) 2013 Peter Steinberger. All rights reserved.
    // Licensed under MIT.
    //
    // You should only use this in debug builds. It doesn't use private API, but I wouldn't ship it.

    #import <objc/runtime.h>
    #import <objc/message.h>
    #import <QuartzCore/QuartzCore.h>

    #if DEBUG
    #define PROPERTY(propName) NSStringFromSelector(@selector(propName))
    #else
    #define PROPERTY(propName) @#propName
    #endif

    // A better assert. NSAssert is too runtime dependant, and assert() doesn't log.
    // http://www.mikeash.com/pyblog/friday-qa-2013-05-03-proper-use-of-asserts.html
    @@ -18,9 +24,6 @@
    NSLog(@"%@", [NSString stringWithFormat: @"Assertion failure: %s in %s on line %s:%d. %@", #expression, __PRETTY_FUNCTION__, __FILE__, __LINE__, [NSString stringWithFormat:@"" __VA_ARGS__]]); \
    abort(); }} while(0)

    // You should only use this in debug builds. It doesn't use private API, but I wouldn't ship it.
    #ifdef DEBUG

    BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) {
    NSCParameterAssert(c && origSEL && newSEL && block);
    Method origMethod = class_getInstanceMethod(c, origSEL);
    @@ -74,6 +77,4 @@ static void PSPDFAssertIfNotMainThread(void) {
    }
    }
    }
    }

    #endif
    }
  12. @steipete steipete revised this gist Jun 2, 2013. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,7 @@
    // Taken from the commercial iOS PDF framework http://pspdfkit.com.
    // Copyright (c) 2013 Peter Steinberger. All rights reserved.
    // Licensed under MIT.

    #import <objc/runtime.h>
    #import <objc/message.h>
    #import <QuartzCore/QuartzCore.h>
  13. @steipete steipete revised this gist Jun 2, 2013. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -58,12 +58,12 @@ static void PSPDFAssertIfNotMainThread(void) {
    SEL selector = NSSelectorFromString(selStr);
    SEL newSelector = NSSelectorFromString([NSString stringWithFormat:@"pspdf_%@", selStr]);
    if ([selStr hasSuffix:@":"]) {
    PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(UIView *_self, CGRect r) {
    PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self, CGRect r) {
    PSPDFAssertIfNotMainThread();
    ((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r);
    });
    }else {
    PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(UIView *_self) {
    PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self) {
    PSPDFAssertIfNotMainThread();
    ((void ( *)(id, SEL))objc_msgSend)(_self, newSelector);
    });
  14. @steipete steipete revised this gist Jun 2, 2013. 1 changed file with 29 additions and 20 deletions.
    49 changes: 29 additions & 20 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -17,21 +17,29 @@
    // You should only use this in debug builds. It doesn't use private API, but I wouldn't ship it.
    #ifdef DEBUG

    static void PSPDFSwizzleMethod(Class c, SEL orig, SEL new) {
    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);
    if (class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
    class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) {
    NSCParameterAssert(c && origSEL && newSEL && block);
    Method origMethod = class_getInstanceMethod(c, origSEL);
    const char *encoding = method_getTypeEncoding(origMethod);

    // Add the new method.
    IMP impl = imp_implementationWithBlock(block);
    if (!class_addMethod(c, newSEL, impl, encoding)) {
    NSLog(@"Failed to add method: %@ on %@", NSStringFromSelector(newSEL), c);
    return NO;
    }else {
    method_exchangeImplementations(origMethod, newMethod);
    }
    }
    // Ensure the new selector has the same parameters as the existing selector.
    Method newMethod = class_getInstanceMethod(c, newSEL);
    NSCAssert(strcmp(method_getTypeEncoding(origMethod), method_getTypeEncoding(newMethod)) == 0, @"Encoding must be the same.");

    void PSPDFReplaceMethod(Class c, SEL orig, SEL newSel, IMP impl) {
    Method method = class_getInstanceMethod(c, orig);
    if (!class_addMethod(c, newSel, impl, method_getTypeEncoding(method))) {
    NSLog(@"Failed to add method: %@ on %@", NSStringFromSelector(newSel), c);
    }else PSPDFSwizzleMethod(c, orig, newSel);
    // If original doesn't implement the method we want to swizzle, create it.
    if (class_addMethod(c, origSEL, method_getImplementation(newMethod), encoding)) {
    class_replaceMethod(c, newSEL, method_getImplementation(origMethod), encoding);
    }else {
    method_exchangeImplementations(origMethod, newMethod);
    }
    }
    return YES;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////
    @@ -46,18 +54,19 @@ static void PSPDFAssertIfNotMainThread(void) {
    // @note No private API is used here.
    __attribute__((constructor)) static void PSPDFUIKitMainThreadGuard(void) {
    @autoreleasepool {
    for (NSString *selector in @[PROPERTY(setNeedsLayout), PROPERTY(setNeedsDisplay), PROPERTY(setNeedsDisplayInRect:)]) {
    SEL newSelector = NSSelectorFromString([NSString stringWithFormat:@"pspdf_%@", selector]);
    if ([selector hasSuffix:@":"]) {
    PSPDFReplaceMethod(UIView.class, NSSelectorFromString(selector), newSelector, imp_implementationWithBlock(^(UIView *_self, CGRect r) {
    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, ^(UIView *_self, CGRect r) {
    PSPDFAssertIfNotMainThread();
    ((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r);
    }));
    });
    }else {
    PSPDFReplaceMethod(UIView.class, NSSelectorFromString(selector), newSelector, imp_implementationWithBlock(^(UIView *_self) {
    PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(UIView *_self) {
    PSPDFAssertIfNotMainThread();
    ((void ( *)(id, SEL))objc_msgSend)(_self, newSelector);
    }));
    });
    }
    }
    }
  15. @steipete steipete revised this gist Jun 2, 2013. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -30,15 +30,15 @@ static void PSPDFSwizzleMethod(Class c, SEL orig, SEL new) {
    void PSPDFReplaceMethod(Class c, SEL orig, SEL newSel, IMP impl) {
    Method method = class_getInstanceMethod(c, orig);
    if (!class_addMethod(c, newSel, impl, method_getTypeEncoding(method))) {
    PSPDFLogError(@"Failed to add method: %@ on %@", NSStringFromSelector(newSel), c);
    NSLog(@"Failed to add method: %@ on %@", NSStringFromSelector(newSel), c);
    }else PSPDFSwizzleMethod(c, orig, newSel);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////
    #pragma mark - Tracks down calls to UIKit from a Thread other than Main

    static 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]);
    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]);
    }

    // This installs a small guard that checks for the most common threading-errors in UIKit.
  16. @steipete steipete revised this gist May 28, 2013. 1 changed file with 10 additions and 12 deletions.
    22 changes: 10 additions & 12 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -48,18 +48,16 @@ static void PSPDFAssertIfNotMainThread(void) {
    @autoreleasepool {
    for (NSString *selector in @[PROPERTY(setNeedsLayout), PROPERTY(setNeedsDisplay), PROPERTY(setNeedsDisplayInRect:)]) {
    SEL newSelector = NSSelectorFromString([NSString stringWithFormat:@"pspdf_%@", selector]);
    for (Class theClass in @[UIView.class, CALayer.class]) {
    if ([selector hasSuffix:@":"]) {
    PSPDFReplaceMethod(theClass, NSSelectorFromString(selector), newSelector, imp_implementationWithBlock(^(UIView *_self, CGRect r) {
    PSPDFAssertIfNotMainThread();
    ((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r);
    }));
    }else {
    PSPDFReplaceMethod(theClass, NSSelectorFromString(selector), newSelector, imp_implementationWithBlock(^(UIView *_self) {
    PSPDFAssertIfNotMainThread();
    ((void ( *)(id, SEL))objc_msgSend)(_self, newSelector);
    }));
    }
    if ([selector hasSuffix:@":"]) {
    PSPDFReplaceMethod(UIView.class, NSSelectorFromString(selector), newSelector, imp_implementationWithBlock(^(UIView *_self, CGRect r) {
    PSPDFAssertIfNotMainThread();
    ((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r);
    }));
    }else {
    PSPDFReplaceMethod(UIView.class, NSSelectorFromString(selector), newSelector, imp_implementationWithBlock(^(UIView *_self) {
    PSPDFAssertIfNotMainThread();
    ((void ( *)(id, SEL))objc_msgSend)(_self, newSelector);
    }));
    }
    }
    }
  17. @steipete steipete revised this gist May 28, 2013. 1 changed file with 10 additions and 0 deletions.
    10 changes: 10 additions & 0 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -4,6 +4,16 @@

    #define PROPERTY(propName) NSStringFromSelector(@selector(propName))

    // A better assert. NSAssert is too runtime dependant, and assert() doesn't log.
    // http://www.mikeash.com/pyblog/friday-qa-2013-05-03-proper-use-of-asserts.html
    // Accepts both:
    // - PSPDFAssert(x > 0);
    // - PSPDFAssert(y > 3, @"Bad value for y");
    #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)

    // You should only use this in debug builds. It doesn't use private API, but I wouldn't ship it.
    #ifdef DEBUG

  18. @steipete steipete revised this gist May 28, 2013. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,8 @@
    #import <objc/message.h>
    #import <QuartzCore/QuartzCore.h>

    #define PROPERTY(propName) NSStringFromSelector(@selector(propName))

    // You should only use this in debug builds. It doesn't use private API, but I wouldn't ship it.
    #ifdef DEBUG

  19. @steipete steipete revised this gist May 28, 2013. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -52,4 +52,5 @@ static void PSPDFAssertIfNotMainThread(void) {
    }
    }
    }

    #endif
  20. @steipete steipete revised this gist May 28, 2013. 1 changed file with 5 additions and 1 deletion.
    6 changes: 5 additions & 1 deletion PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,9 @@
    #import <objc/message.h>
    #import <QuartzCore/QuartzCore.h>

    // You should only use this in debug builds. It doesn't use private API, but I wouldn't ship it.
    #ifdef DEBUG

    static void PSPDFSwizzleMethod(Class c, SEL orig, SEL new) {
    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);
    @@ -48,4 +51,5 @@ static void PSPDFAssertIfNotMainThread(void) {
    }
    }
    }
    }
    }
    #endif
  21. @steipete steipete created this gist May 28, 2013.
    51 changes: 51 additions & 0 deletions PSPDFUIKitMainThreadGuard.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,51 @@
    #import <objc/runtime.h>
    #import <objc/message.h>
    #import <QuartzCore/QuartzCore.h>

    static void PSPDFSwizzleMethod(Class c, SEL orig, SEL new) {
    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);
    if (class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
    class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    }else {
    method_exchangeImplementations(origMethod, newMethod);
    }
    }

    void PSPDFReplaceMethod(Class c, SEL orig, SEL newSel, IMP impl) {
    Method method = class_getInstanceMethod(c, orig);
    if (!class_addMethod(c, newSel, impl, method_getTypeEncoding(method))) {
    PSPDFLogError(@"Failed to add method: %@ on %@", NSStringFromSelector(newSel), c);
    }else PSPDFSwizzleMethod(c, orig, newSel);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////
    #pragma mark - Tracks down calls to UIKit from a Thread other than Main

    static 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]);
    }

    // This installs a small guard that checks for the most common threading-errors in UIKit.
    // This won't really slow down performance but still only is compiled in DEBUG versions of PSPDFKit.
    // @note No private API is used here.
    __attribute__((constructor)) static void PSPDFUIKitMainThreadGuard(void) {
    @autoreleasepool {
    for (NSString *selector in @[PROPERTY(setNeedsLayout), PROPERTY(setNeedsDisplay), PROPERTY(setNeedsDisplayInRect:)]) {
    SEL newSelector = NSSelectorFromString([NSString stringWithFormat:@"pspdf_%@", selector]);
    for (Class theClass in @[UIView.class, CALayer.class]) {
    if ([selector hasSuffix:@":"]) {
    PSPDFReplaceMethod(theClass, NSSelectorFromString(selector), newSelector, imp_implementationWithBlock(^(UIView *_self, CGRect r) {
    PSPDFAssertIfNotMainThread();
    ((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r);
    }));
    }else {
    PSPDFReplaceMethod(theClass, NSSelectorFromString(selector), newSelector, imp_implementationWithBlock(^(UIView *_self) {
    PSPDFAssertIfNotMainThread();
    ((void ( *)(id, SEL))objc_msgSend)(_self, newSelector);
    }));
    }
    }
    }
    }
    }