Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Practical Runtime Hackery

Practical Runtime Hackery

NSSpain 2013 - Logroño.

Join me on a tour that shows unconventional yet elegant real-world solutions to common problems on application design, creative ways in utilizing the iOS runtime, smoothy back-port code for older iOS versions and much more. Knowledge of the runtime will enable you to write less code, work faster and above all - it's a lot of fun!

Without the notes, some context will be lost. It's important to understand that some of the approaches here might not be worth it in smaller projects, but they help to reduce the complexity throughout the project and bundle them into one file - with the result that the parts you work on everyday are simpler, even though the new parts might be much more complex. So overall, it *reduces* the total lines of code, and/or their perceived complexity of the system.

Peter Steinberger

September 18, 2013
Tweet

More Decks by Peter Steinberger

Other Decks in Programming

Transcript

  1. Practical Runtime Hackery
    Peter Steinberger
    @steipete

    View full-size slide

  2. PSPDFKit
    iOS PDF Framework

    View full-size slide

  3. Use iOS 7 methods on iOS 5/6
    PSPDFModernizer.h

    View full-size slide

  4. #if  __IPHONE_OS_VERSION_MIN_REQUIRED  <  70000
     
    @interface  UIView  (PSPDFModernizer)
     
    +  (void)performWithoutAnimation:(void  (^)(void))action;
     
    @end
     
    #endif

    View full-size slide

  5. #if  __IPHONE_OS_VERSION_MIN_REQUIRED  <  70000
     
    @interface  UIView  (PSPDFModernizer)
     
    +  (void)performWithoutAnimation:(void  (^)(void))action;
     
    @end
     
    #endif

    View full-size slide

  6. IMP  impl  =  imp_implementationWithBlock(^(Class  
    theClass,  dispatch_block_t  block)  {
               if  (block)  {
                         [CATransaction  begin];
                         [CATransaction  setDisableActions:YES];
                         block();
                         [CATransaction  commit];
               }
    });

    View full-size slide

  7. IMP  impl  =  imp_implementationWithBlock(^(Class  
    theClass,  dispatch_block_t  block)  {
               if  (block)  {
                         [CATransaction  begin];
                         [CATransaction  setDisableActions:YES];
                         block();
                         [CATransaction  commit];
               }
    });

    View full-size slide

  8. __attribute__((constructor))  static  void  PSPDFAddPerformWithoutAnimation(void)  {
           SEL  performWithoutAnimationSEL  =  @selector(performWithoutAnimation:);
           if  (![UIView  respondsToSelector:performWithoutAnimationSEL])  {
                   Class  metaClass  =  object_getClass(UIView.class);
                   class_addMethod(metaClass,  performWithoutAnimationSEL,  impl,  "v@:@?");
           }
    }

    View full-size slide

  9. __attribute__((constructor))  static  void  PSPDFAddPerformWithoutAnimation(void)  {
           SEL  performWithoutAnimationSEL  =  @selector(performWithoutAnimation:);
           if  (![UIView  respondsToSelector:performWithoutAnimationSEL])  {
                   Class  metaClass  =  object_getClass(UIView.class);
                   class_addMethod(metaClass,  performWithoutAnimationSEL,  impl,  "v@:@?");
           }
    }

    View full-size slide

  10. __attribute__((constructor))

    View full-size slide

  11. “The only reason why they [the numbers] still exist is for binary
    compatibility; there are bits of esoteric code here and there that
    still parse the type encoding string with the expectation that
    there will be random numbers sprinkled here and there.”
    - Apple

    View full-size slide

  12. +  (void)performWithoutAnimation:(void  (^)(void))actionsWithoutAnimation  NS_AVAILABLE_IOS(7_0);
    v@:@?

    View full-size slide

  13. v@:@?
    void  methImpl_static_UIView_performWithoutAnimation_(id  self,  SEL  selector,  dispatch_block_t  block);

    View full-size slide

  14. v
    @:@?
    void  methImpl_static_UIView_performWithoutAnimation_(id  self,  SEL  selector,  dispatch_block_t  block);

    View full-size slide

  15. v @
    :@?
    void  methImpl_static_UIView_performWithoutAnimation_(id  self,  SEL  selector,  dispatch_block_t  block);

    View full-size slide

  16. v @ :
    @?
    void  methImpl_static_UIView_performWithoutAnimation_(id  self,  SEL  selector,  dispatch_block_t  block);

    View full-size slide

  17. v @ : @?
    void  methImpl_static_UIView_performWithoutAnimation_(id  self,  SEL  selector,  dispatch_block_t  block);

    View full-size slide

  18. v44@0:4d8d16f24f28I32@?36@?40
    + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay
    usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity
    options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:
    (void (^)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);

    View full-size slide

  19. __attribute__((constructor))  static  void  PSPDFKitAddPerformWithoutAnimation(void)  {
           @autoreleasepool  {
                   SEL  performWithoutAnimationSEL  =  @selector(performWithoutAnimation:);
                   if  (![UIView  respondsToSelector:performWithoutAnimationSEL])  {
                           IMP  impl  =  imp_implementationWithBlock(^(Class  theClass,  dispatch_block_t  block)  {
                                   [CATransaction  begin];
                                   [CATransaction  setDisableActions:YES];
                                   if  (block)  block();
                                   [CATransaction  commit];
                           });
                           Class  metaClass  =  object_getClass(UIView.class);
                           if  (!class_addMethod(metaClass,  performWithoutAnimationSEL,  impl,  "v@:@?"))  {
                                   NSLog(@"Failed  to  add  performWithoutAnimationSEL:  to  UIView.");
                           }
                   }
           }
    }

    View full-size slide

  20. Fast runtime queries
    PSPDFAnnotationMapper.h

    View full-size slide

  21. @interface  PSPDFAnnotationMapper  :  NSObject
     
    +  (instancetype)defaultMapper;
     
    -­‐  (Class)annotationClassForType:(PSPDFAnnotationType)type;
     
    @end

    View full-size slide

  22. Class  *objc_copyClassList(unsigned  int  *outCount)
     __OSX_AVAILABLE_STARTING(__MAC_10_7,  __IPHONE_3_1);

    View full-size slide

  23. [NSObject  isSubclassOfClass:]

    View full-size slide

  24. [NSObject  isSubclassOfClass:]
    = bad idea

    View full-size slide

  25. NS_INLINE  BOOL  PSPDFIsSubclassOfClass(Class  subclass,  Class  superclass)
    {
           for  (Class  class  =  class_getSuperclass(subclass);  class  !=  Nil;
                     class  =  class_getSuperclass(class))  {
                   if  (class  ==  superclass)  return  YES;
           }
           return  NO;
    }

    View full-size slide

  26. NS_INLINE  BOOL  PSPDFIsSubclassOfClass(Class  subclass,  Class  superclass)
    {
           for  (Class  class  =  class_getSuperclass(subclass);  class  !=  Nil;
                     class  =  class_getSuperclass(class))  {
                   if  (class  ==  superclass)  return  YES;
           }
           return  NO;
    }

    View full-size slide

  27. inline is a lie
    __inline__ __attribute__((always_inline))

    View full-size slide

  28. NS_INLINE  BOOL  PSPDFIsSubclassOfClass(Class  subclass,  Class  superclass)
    {
           for  (Class  class  =  class_getSuperclass(subclass);  class  !=  Nil;
                     class  =  class_getSuperclass(class))  {
                   if  (class  ==  superclass)  return  YES;
           }
           return  NO;
    }

    View full-size slide

  29. NSDictionary  *PSPDFBuildMapOfSubclassesForClass(Class  superclass)  {
           NSMutableDictionary  *typeToClassMutable  =  [NSMutableDictionary  dictionary];
           unsigned  int  count  =  0;
           Class  *classList  =  objc_copyClassList(&count);
           for  (int  index  =  0;  index  <  count;  ++index)  {
                   Class  class  =  classList[index];
                   if  (class  !=  superclass  &&  PSPDFIsSubclassOfClass(class,  superclass))  {
                           NSString  *supportedType  =  [class  supportedType];
                           typeToClassMutable[supportedType]  =  class;
                   }
           }
     free(classList);
           return  typeToClassMutable;
    }

    View full-size slide

  30. More convenient delegate calling
    PSPDFDelegateProxy.h

    View full-size slide

  31. if  ([self.delegate  respondsToSelector:@selector(annotationStyleController:willStartChangingProperty:)])  {
           self.delegate  annotationStyleController:self  willStartChangingProperty:propertyName];
    }

    View full-size slide

  32. -Warc-repeated-use-of-weak

    View full-size slide

  33. id  delegate  =  self.delegate;
    if  ([delegate  respondsToSelector:@selector(annotationStyleController:willStartChangingProperty:)])  {
           delegate  annotationStyleController:self  willStartChangingProperty:propertyName];
    }

    View full-size slide

  34. BOOL  accepted  =  YES;        
    id  delegate  =  self.delegate;
    if  ([delegate  respondsToSelector:@selector(selectionView:shouldStartSelectionAtPoint:)])  {
           accepted  =  [delegate  selectionView:self  shouldStartSelectionAtPoint:location];
    }

    View full-size slide

  35. [self.delegateProxy  annotationStyleController:self  willStartChangingProperty:propertyName];

    View full-size slide

  36. BOOL  accepted  =  [[(id)self.delegateProxy  copyThatDefaultsToYES]
                                     selectionView:self  shouldStartSelectionAtPoint:location];

    View full-size slide

  37. @interface  PSPDFDelegateProxy  :  NSProxy
     
    -­‐  (id)initWithDelegate:(id)delegate  conformingToProtocol:(Protocol  *)protocol
                                                                               defaultReturnValue:(NSValue  *)returnValue;
     
    -­‐  (instancetype)copyThatDefaultsTo:(NSValue  *)defaultValue;
    -­‐  (instancetype)copyThatDefaultsToYES;
       
    @end

    View full-size slide

  38. -­‐  (id)forwardingTargetForSelector:(SEL)aSelector  __OSX_AVAILABLE_STARTING(__MAC_10_5,  __IPHONE_2_0);
    -­‐  (NSMethodSignature  *)methodSignatureForSelector:(SEL)aSelector;
    -­‐  (void)forwardInvocation:(NSInvocation  *)invocation
    Message forwarding 101

    View full-size slide

  39. -­‐  (id)forwardingTargetForSelector:(SEL)aSelector  __OSX_AVAILABLE_STARTING(__MAC_10_5,  __IPHONE_2_0);
    -­‐  (NSMethodSignature  *)methodSignatureForSelector:(SEL)aSelector;
    -­‐  (void)forwardInvocation:(NSInvocation  *)invocation
    Message forwarding 101

    View full-size slide

  40. -­‐  (BOOL)respondsToSelector:(SEL)selector  {
           return  [_delegate  respondsToSelector:selector];
    }

    View full-size slide

  41. -­‐  (id)forwardingTargetForSelector:(SEL)selector  {
           id  delegate  =  _delegate;
           return  [delegate  respondsToSelector:selector]  ?  delegate  :  self;
    }

    View full-size slide

  42. -­‐  (NSMethodSignature  *)methodSignatureForSelector:(SEL)selector  {
           NSMethodSignature  *signature  =  [_delegate  methodSignatureForSelector:selector];
           
           return  signature;
    }

    View full-size slide

  43. -­‐  (NSMethodSignature  *)methodSignatureForSelector:(SEL)selector  {
           NSMethodSignature  *signature  =  [_delegate  methodSignatureForSelector:selector];
           
           return  signature;
    }
           //  oh-­‐ooh.
           if  (!signature)  {
                   if  (!_signatures)  _signatures  =  [self  methodSignaturesForProtocol:_protocol];
                   signature  =  CFDictionaryGetValue(_signatures,  selector);
           }

    View full-size slide

  44. -­‐  (NSMethodSignature  *)_searchAllClassesForSignature:(SEL)sel
    {
           int  count  =  objc_getClassList(NULL,  0);
           Class  *classes  =  malloc(sizeof(*classes)  *  count);
           objc_getClassList(classes,  count);
           
           NSMethodSignature  *sig  =  nil;
           for(int  i  =  0;  i  <  count;  i++)
           {
                   Class  c  =  classes[i];
                   if  (class_getClassMethod(c,  @selector(methodSignatureForSelector:))  &&  
                         class_getClassMethod(c,  @selector(instanceMethodSignatureForSelector:)))
                   {
                           NSMethodSignature  *thisSig  =  [c  methodSignatureForSelector:  sel];
                           if  (!sig)  sig  =  thisSig;
                           else  if  (sig  &&  thisSig  &&  ![sig  isEqual:  thisSig])
                           {
                                   sig  =  nil;  break;
                           }
                           
                           thisSig  =  [c  instanceMethodSignatureForSelector:  sel];
                           if  (!sig)  sig  =  thisSig;
                           else  if(sig  &&  thisSig  &&  ![sig  isEqual:  thisSig])
                           {
                                   sig  =  nil;  break;
                           }
                   }
           }
           free(classes);
           return  sig;
    }

    View full-size slide

  45. struct  objc_method_description  *protocol_copyMethodDescriptionList(
         Protocol  *p,  BOOL  isRequiredMethod,  BOOL  isInstanceMethod,  unsigned  int  *outCount)

    View full-size slide

  46. struct  objc_method_description  *protocol_copyMethodDescriptionList(
         Protocol  *p,  BOOL  isRequiredMethod,  BOOL  isInstanceMethod,  unsigned  int  *outCount)
    struct  objc_method_description  {
         SEL  name;  
      char  *types;
    };

    View full-size slide

  47. struct  objc_method_description  *protocol_copyMethodDescriptionList(
         Protocol  *p,  BOOL  isRequiredMethod,  BOOL  isInstanceMethod,  unsigned  int  *outCount)
    struct  objc_method_description  {
         SEL  name;  
      char  *types;
    };
    +  (NSMethodSignature  *)signatureWithObjCTypes:(const  char  *)types;

    View full-size slide

  48. -­‐  (void)methodSignaturesForProtocol:(Protocol  *)protocol  inDictionary:(CFMutableDictionaryRef)cache  {
           void  (^enumerateRequiredMethods)(BOOL)  =  ^(BOOL  isRequired)  {
                   unsigned  int  methodCount;
                   struct  objc_method_description  *descr  =  protocol_copyMethodDescriptionList(protocol,  isRequired,
                                                                                                                                                                         YES,  &methodCount);
                   for  (NSUInteger  idx  =  0;  idx  <  methodCount;  idx++)  {
                           NSMethodSignature  *signature  =  [NSMethodSignature  signatureWithObjCTypes:descr[idx].types];
                           CFDictionarySetValue(cache,  descr[idx].name,  (__bridge  const  void  *)(signature));
                   }
                   free(descr);
           };
           
           enumerateRequiredMethods(NO);
           enumerateRequiredMethods(YES);
    }

    View full-size slide

  49. -­‐  (void)methodSignaturesForProtocol:(Protocol  *)protocol  inDictionary:(CFMutableDictionaryRef)cache  {
           void  (^enumerateRequiredMethods)(BOOL)  =  ^(BOOL  isRequired)  {
                   unsigned  int  methodCount;
                   struct  objc_method_description  *descr  =  protocol_copyMethodDescriptionList(protocol,  isRequired,
                                                                                                                                                                         YES,  &methodCount);
                   for  (NSUInteger  idx  =  0;  idx  <  methodCount;  idx++)  {
                           NSMethodSignature  *signature  =  [NSMethodSignature  signatureWithObjCTypes:descr[idx].types];
                           CFDictionarySetValue(cache,  descr[idx].name,  (__bridge  const  void  *)(signature));
                   }
                   free(descr);
           };
           
           enumerateRequiredMethods(NO);
           enumerateRequiredMethods(YES);
    }
           unsigned  int  inheritedProtocolCount;
           Protocol  *__unsafe_unretained*  inheritedProtocols  =  protocol_copyProtocolList(protocol,  &inheritedProtocolCount);
           for  (NSUInteger  idx  =  0;  idx  <  inheritedProtocolCount;  idx++)  {
                   [self  methodSignaturesForProtocol:inheritedProtocols[idx]  inDictionary:cache];
           }
           free(inheritedProtocols);

    View full-size slide

  50. static  CFMutableDictionaryRef  _protocolCache  =  nil;
    static  OSSpinLock  _lock  =  OS_SPINLOCK_INIT;
     
    -­‐  (CFDictionaryRef)methodSignaturesForProtocol:(Protocol  *)protocol  {
           OSSpinLockLock(&_lock);
           
           if  (!_protocolCache)  _protocolCache  =  CFDictionaryCreateMutable(NULL,  0,  NULL,  &kCFTypeDictionaryValueCallBacks);
           CFDictionaryRef  signatureCache  =  CFDictionaryGetValue(_protocolCache,  (__bridge  const  void  *)(protocol));
     
           if  (!signatureCache)  {
                   signatureCache  =  CFDictionaryCreateMutable(NULL,  0,  NULL,  &kCFTypeDictionaryValueCallBacks);
                   [self  methodSignaturesForProtocol:protocol  inDictionary:(CFMutableDictionaryRef)signatureCache];
                   CFDictionarySetValue(_protocolCache,  (__bridge  const  void  *)(protocol),  signatureCache);
                   CFRelease(signatureCache);
           }
           OSSpinLockUnlock(&_lock);
           return  signatureCache;
    }

    View full-size slide

  51. id  delegate  =  self.delegate;
    if  ([delegate  respondsToSelector:@selector(annotationStyleController:willStartChangingProperty:)])  {
           delegate  annotationStyleController:self  willStartChangingProperty:propertyName];
    }

    View full-size slide

  52. [self.delegateProxy  annotationStyleController:self  willStartChangingProperty:propertyName];

    View full-size slide

  53. BOOL  accepted  =  YES;        
    id  delegate  =  self.delegate;
    if  ([delegate  respondsToSelector:@selector(selectionView:shouldStartSelectionAtPoint:)])  {
           accepted  =  [delegate  selectionView:self  shouldStartSelectionAtPoint:location];
    }

    View full-size slide

  54. BOOL  accepted  =  [[(id)self.delegateProxy  copyThatDefaultsToYES]
                                     selectionView:self  shouldStartSelectionAtPoint:location];

    View full-size slide

  55. -­‐  (void)forwardInvocation:(NSInvocation  *)invocation  {
           if  (_defaultReturnValue)  {
                   const  char  *methodReturnType  =  invocation.methodSignature.methodReturnType;
                   if  (strcmp(_defaultReturnValue.objCType,  methodReturnType)  ==  0)  {
                           char  buffer[invocation.methodSignature.methodReturnLength];
                           [_defaultReturnValue  getValue:buffer];
                           [invocation  setReturnValue:&buffer];
                   }else  {
                           PSPDFLogError(@"Unexpected  invocation  method  signature.”);
                   }
           }
    }

    View full-size slide

  56. -­‐  (void)forwardInvocation:(NSInvocation  *)invocation  {
           if  (_defaultReturnValue)  {
                   const  char  *methodReturnType  =  invocation.methodSignature.methodReturnType;
                   if  (strcmp(_defaultReturnValue.objCType,  methodReturnType)  ==  0  ||
                           (strcmp(_defaultReturnValue.objCType,  "c")  ==  0  &&  strcmp(methodReturnType,  "B")  ==  0))  {
                           char  buffer[invocation.methodSignature.methodReturnLength];
                           [_defaultReturnValue  getValue:buffer];
                           [invocation  setReturnValue:&buffer];
                   }else  {
                           PSPDFLogError(@"Unexpected  invocation  method  signature.”);
                   }
           }
    }

    View full-size slide

  57. https://github.com/steipete/PSTDelegateProxy

    View full-size slide

  58. When dynamic subclassing
    actually saves you code
    UIViewController+PSPDFKitAdditions.h

    View full-size slide

  59. extern  NSString  *const  PSPDFPresentOptionRect;
    extern  NSString  *const  PSPDFPresentOptionPopoverContentSize;
    extern  NSString  *const  PSPDFPresentOptionAllowedPopoverArrowDirections;
    extern  NSString  *const  PSPDFPresentOptionModalPresentationStyle;
    extern  NSString  *const  PSPDFPresentOptionAlwaysModal;
    extern  NSString  *const  PSPDFPresentOptionAlwaysPopover;
    extern  NSString  *const  PSPDFPresentOptionPassthroughViews;
    extern  NSString  *const  PSPDFPresentOptionWillDismissBlock;
    extern  NSString  *const  PSPDFPresentOptionHalfModalMode;
    extern  NSString  *const  PSPDFPresentOptionPersistentCloseButtonMode;
     
    -­‐  (id)presentModalOrInPopover:(UIViewController  *)controller
               embeddedInNavigationController:(BOOL)embedded
                                             withCloseButton:(BOOL)closeButton
                                                           animated:(BOOL)animated
                                                               sender:(id)sender
                                                             options:(NSDictionary  *)options;

    View full-size slide

  60. @interface  PSPDFNavigationController  :  UINavigationController
    @property  (nonatomic,  copy)  dispatch_block_t  navigationControllerWillDismissAction;

    View full-size slide

  61. -­‐  (void)viewWillDisappear:(BOOL)animated  {
           [super  viewWillDisappear:animated];
     
           //  Execute  the  dismiss  action  if  we're  being  dismissed.
           if  (self.isBeingDismissed  &&  self.navigationControllerWillDismissAction)  {
                   self.navigationControllerWillDismissAction();
           }
    }

    View full-size slide

  62. @interface  UIImagePickerController  :  UINavigationController
    @interface  MFMailComposeViewController  :  UINavigationController
    @interface  MFMessageComposeViewController  :  UINavigationController
    @interface  ABPeoplePickerNavigationController  :  UINavigationController

    View full-size slide

  63. -­‐  (void)pspdf_addWillDismissAction:(dispatch_block_t)dismissAction;

    View full-size slide

  64. /**  
     *  Sets  the  class  of  an  object.
     *  
     *  @param  obj  The  object  to  modify.
     *  @param  cls  A  class  object.
     *  
     *  @return  The  previous  value  of  \e  object's  class,  or  \c  Nil  if  \e  object  is  \c  nil.
     */
    OBJC_EXPORT  Class  object_setClass(id  obj,  Class  cls);

    View full-size slide

  65. IMP  viewWillDismissIMP  =  imp_implementationWithBlock(^(UIViewController  *_self,  BOOL  animated)  {
           dispatch_block_t  dismissBlock  =  objc_getAssociatedObject(_self,  &PSPDFVCWillDismissActionKey);
                     if  (_self.isBeingDismissed  &&  dismissBlock)  dismissBlock();
           });
    }

    View full-size slide

  66. ... super?
    void  (*superIMP)(id,  SEL,  BOOL)  =  
           (void  *)[_self.class.superclass  instanceMethodForSelector:origSEL];
    superIMP(_self,  _cmd,  animated);

    View full-size slide

  67. IMP  viewWillDisappearIMP  =  imp_implementationWithBlock(^(UIViewController  *_self,  BOOL  animated)  {
           void  (*superIMP)(id,  SEL,  BOOL)  =  (void  *)[_self.class.superclass  instanceMethodForSelector:origSEL];
           superIMP(_self,  _cmd,  animated);
     
           dispatch_block_t  dismissBlock  =  objc_getAssociatedObject(_self,  &  PSPDFVCWillDismissActionKey);
                     if  (_self.isBeingDismissed  &&  dismissBlock)  dismissBlock();
           });
    }

    View full-size slide

  68. 1. NSString  *subclassName  =  [NSString  stringWithFormat:@"PSPDF_%@",  
    NSStringFromClass(self.class)];
    2. Class  subclass  =  objc_allocateClassPair(self.class,  subclassName.UTF8String,  0);
    3. class_addMethod(subclass,  origSEL,  viewIMP,  
    method_getTypeEncoding(class_getInstanceMethod(self.class,  origSEL)));
    4. objc_registerClassPair(subclass);
    5. object_setClass(self,  subclass);
    6. objc_setAssociatedObject(self,  &  PSPDFVCWillDismissActionKey,  dismissAction,  
    OBJC_ASSOCIATION_COPY_NONATOMIC);

    View full-size slide

  69. Edge Cases...

    View full-size slide

  70. Caveat ARC'er: objc_allocateClassPair() cannot be compiled with ARC.

    View full-size slide

  71. Caveat ARC'er: objc_allocateClassPair() cannot be compiled with ARC.
    -fno-objc-arc

    View full-size slide

  72. Caveat ARC'er: objc_allocateClassPair() cannot be compiled with ARC.
    -fno-objc-arc
    Fixed in iOS 6

    View full-size slide

  73. self.class  !=  object_getClass(self);

    View full-size slide

  74. NSKVONotifying_

    View full-size slide

  75. BOOL  PSPDFReplaceMethodWithBlock(Class  c,  SEL  origSEL,  SEL  newSEL,  id  block)  {
           PSPDFAssert(c  &&  origSEL  &&  newSEL  &&  block);
           if  ([c  respondsToSelector:newSEL])  return  YES;
     
           Method  origMethod  =  class_getInstanceMethod(c,  origSEL);
           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  (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;
    }

    View full-size slide

  76. -­‐  (void)pspdf_addWillDismissAction:(dispatch_block_t)dismissAction  {
           @synchronized(self)  {
                   //  First  try  to  get  an  already  defined  block  and  add  chain  the  blocks  if  set.
                   dispatch_block_t  currentDismissBlock  =  objc_getAssociatedObject(self,  &PSPDFViewControllerWillDismissActionKey);
                   if  (currentDismissBlock)  {
                           currentDismissBlock  =  ^{  currentDismissBlock();  dismissAction();  };
                           objc_setAssociatedObject(self,  &PSPDFViewControllerWillDismissActionKey,  currentDismissBlock,  OBJC_ASSOCIATION_COPY_NONATOMIC);
                   }else  {
                           SEL  origSEL  =  @selector(viewWillDisappear:);
     
                           //  Prepare  the  new  method.
                           id  block  =  ^(UIViewController  *_self,  BOOL  animated)  {
                                   //  Call  [super  viewWillDisappear:animated]
                                   void  (*superIMP)(id,  SEL,  BOOL)  =  (void  *)[_self.class.superclass  instanceMethodForSelector:origSEL];
                                   superIMP(_self,  _cmd,  animated);
     
                                   //  Execute  the  dismiss  action  if  we're  being  dismissed.
                                   dispatch_block_t  dismissBlock  =  objc_getAssociatedObject(_self,  &PSPDFViewControllerWillDismissActionKey);
                                   if  (_self.isBeingDismissed  &&  dismissBlock)  dismissBlock();
                           };
     
                           //  If  the  class  is  lying  about  their  class,  it's  most  likely  KVO.
                           if  (self.class  !=  object_getClass(self))  {
                                   SEL  newSEL  =  NSSelectorFromString([NSString  stringWithFormat:@"pspdf_%@",  NSStringFromSelector(origSEL)]);
                                   if  (PSPDFReplaceMethodWithBlock(self.class,  origSEL,  newSEL,  block))  {
                                           objc_setAssociatedObject(self,  &PSPDFViewControllerWillDismissActionKey,  dismissAction,  OBJC_ASSOCIATION_COPY_NONATOMIC);
                                   }
                           }else  {
                                   //  Make  a  dynamic  subclass  so  we  can  hook  onto  viewWillDisappear.
                                   const  char  *prefix  =  "PSPDF_";
                                   NSString  *className  =  NSStringFromClass(self.class);
                                   if  (strncmp(prefix,  className.UTF8String,  strlen(prefix))  ==  0)  {  return;  }  //  Error!
     
                                   NSString  *subclassName  =  [NSString  stringWithFormat:@"%s%@",  prefix,  className];
                                   Class  subclass  =  NSClassFromString(subclassName);
     
                                   if  (subclass  ==  nil)  {
                                           subclass  =  objc_allocateClassPair(self.class,  subclassName.UTF8String,  0);
                                           if  (subclass  !=  nil)  {
                                                   IMP  viewWillDisappearIMP  =  imp_implementationWithBlock(block);
                                                   class_addMethod(subclass,  origSEL,  viewWillDisappearIMP,  method_getTypeEncoding(class_getInstanceMethod(self.class,  origSEL)));
                                                   objc_registerClassPair(subclass);
                                           }
                                   }
                                   if  (subclass)  {
                                           object_setClass(self,  subclass);
                                           objc_setAssociatedObject(self,  &PSPDFViewControllerWillDismissActionKey,  dismissAction,  OBJC_ASSOCIATION_COPY_NONATOMIC);
                                   }
                           }
                   }
           }

    View full-size slide

  77. ReactiveCocoa

    View full-size slide

  78. -­‐  (RACSignal  *)rac_signalForSelector:(SEL)selector;

    View full-size slide

  79. PSPDFAnnotationMapper

    View full-size slide

  80. PSPDFModernizer
    PSPDFAnnotationMapper

    View full-size slide

  81. PSPDFModernizer
    PSPDFAnnotationMapper
    PSPDFDelegateProxy

    View full-size slide

  82. PSPDFModernizer
    PSPDFAnnotationMapper
    PSPDFDelegateProxy
    UIViewController+PSPDFKitAdditions

    View full-size slide

  83. Complexity
    Simplicity

    View full-size slide

  84. Peter Steinberger
    @steipete

    View full-size slide