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. #if  __IPHONE_OS_VERSION_MIN_REQUIRED  <  70000   @interface  UIView  (PSPDFModernizer)   +

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

     (void)performWithoutAnimation:(void  (^)(void))action;   @end   #endif
  3. IMP  impl  =  imp_implementationWithBlock(^(Class   theClass,  dispatch_block_t  block)  {  

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

             if  (block)  {                      [CATransaction  begin];                      [CATransaction  setDisableActions:YES];                      block();                      [CATransaction  commit];            } });
  5. __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@:@?");        } }
  6. __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@:@?");        } }
  7. “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
  8. __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.");                        }                }        } }
  9. 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; }
  10. 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; }
  11. 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; }
  12. 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; }
  13. BOOL  accepted  =  YES;         id<PSPDFSelectionViewDelegate>  delegate

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

                           selectionView:self  shouldStartSelectionAtPoint:location];
  15. @interface  PSPDFDelegateProxy  :  NSProxy   -­‐  (id)initWithDelegate:(id)delegate  conformingToProtocol:(Protocol  *)protocol  

                                                                             defaultReturnValue:(NSValue  *)returnValue;   -­‐  (instancetype)copyThatDefaultsTo:(NSValue  *)defaultValue; -­‐  (instancetype)copyThatDefaultsToYES;     @end
  16. -­‐  (id)forwardingTargetForSelector:(SEL)selector  {        id  delegate  =  _delegate;

           return  [delegate  respondsToSelector:selector]  ?  delegate  :  self; }
  17. -­‐  (NSMethodSignature  *)methodSignatureForSelector:(SEL)selector  {        NSMethodSignature  *signature  =

     [_delegate  methodSignatureForSelector:selector];                return  signature; }
  18. -­‐  (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);        }
  19. -­‐  (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; }
  20. struct  objc_method_description  *protocol_copyMethodDescriptionList(      Protocol  *p,  BOOL  isRequiredMethod,  BOOL

     isInstanceMethod,  unsigned  int  *outCount) struct  objc_method_description  {      SEL  name;     char  *types; };
  21. 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;
  22. -­‐  (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); }
  23. -­‐  (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);
  24. 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; }
  25. BOOL  accepted  =  YES;         id<PSPDFSelectionViewDelegate>  delegate

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

                           selectionView:self  shouldStartSelectionAtPoint:location];
  27. -­‐  (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.”);                }        } }
  28. -­‐  (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.”);                }        } }
  29. 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;
  30. -­‐  (void)viewWillDisappear:(BOOL)animated  {        [super  viewWillDisappear:animated];    

         //  Execute  the  dismiss  action  if  we're  being  dismissed.        if  (self.isBeingDismissed  &&  self.navigationControllerWillDismissAction)  {                self.navigationControllerWillDismissAction();        } }
  31. /**    *  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);
  32. IMP  viewWillDismissIMP  =  imp_implementationWithBlock(^(UIViewController  *_self,  BOOL  animated)  {    

       dispatch_block_t  dismissBlock  =  objc_getAssociatedObject(_self,  &PSPDFVCWillDismissActionKey);                  if  (_self.isBeingDismissed  &&  dismissBlock)  dismissBlock();        }); }
  33. ... super? void  (*superIMP)(id,  SEL,  BOOL)  =      

       (void  *)[_self.class.superclass  instanceMethodForSelector:origSEL]; superIMP(_self,  _cmd,  animated);
  34. 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();        }); }
  35. 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);
  36. KVO

  37. 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; }
  38. -­‐  (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);                                }                        }                }        }