Building Aspects

Building Aspects

My talk for Cocoaheads Stockholm, May 2014.

Think of Aspects as method swizzling on steroids. It allows you to add code to existing methods per class or per instance. Aspects automatically deals with calling super and is a lot easier to use than regular method swizzling.

832ece085bfe2c7c5b0ed6be62d7e675?s=128

Peter Steinberger

May 19, 2014
Tweet

Transcript

  1. Building Aspects Cocoaheads Stockholm, May 2014 Peter Steinberger, @steipete

  2. Aspects A delightful, simple library for aspect oriented programming. -

    (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error;
  3. Aspects typedef NS_OPTIONS(NSUInteger, AspectOptions) { AspectPositionAfter = 0, AspectPositionInstead =

    1, AspectPositionBefore = 2, AspectOptionAutomaticRemoval = 1 << 3, AspectOptionPreferClassMethod = 1 << 4 }; - (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error;
  4. Debugging [_singleTapGesture aspect_hookSelector:@selector(setState:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) { NSLog(@"%@: %@", aspectInfo.instance,

    aspectInfo.arguments); }];
  5. View Controller hooks - (void)pspdf_addWillDismissAction:(void (^)(void))action { [self aspect_hookSelector:@selector(viewWillDisappear:) withOptions:AspectPositionAfter

    usingBlock:^(id<AspectInfo> aspectInfo) { if ([aspectInfo.instance isBeingDismissed]) { action(); } }]; }
  6. Testing [WebServiceClient aspect_hookSelector:@selector(sharedInstance) withOptions:AspectPositionInstead usingBlock:^id { return mockedClient; }];

  7. Analytics [ARAnalytics setupWithConfiguration: @{ ARAnalyticsTrackedScreens: @[ @{ ARAnalyticsClass: UIViewController.class, ARAnalyticsDetails:

    @[ @{ ARAnalyticsDetails: @[ @{ ARAnalyticsEventName: @"button pressed", ARAnalyticsSelectorName: @"buttonPressed:"), }] }],
  8. Message forwarding 1) (id)forwardingTargetForSelector:(SEL)aSelector 2) (NSMethodSignature *)methodSignatureForSelector:(SEL)s 3) (void)forwardInvocation:(NSInvocation *)anInvocation

  9. Here be magic!

  10. _objc_msgForward

  11. _objc_msgForward _objc_msgForward_stret

  12. Apple DTS to the rescue?

  13. http://llvm.org/releases/3.4/clang-3.4.src.tar.gz cfe-3.4.1.src/lib/CodeGen/TargetInfo.cpp search for "classifyReturnType"

  14. Swizzle all the things!

  15. Swizzle all the things! - (void)forwardInvocation:(NSInvocation *)invocation; + (Class)class; -

    (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
  16. None
  17. forwardInvocation: void __ASPECTS_ARE_BEING_CALLED__(NSObject *self, SEL selector, NSInvocation *invocation);

  18. Dynamic Subclassing

  19. Dynamic Subclassing Used when we add aspects to a specific

    object. NSString *subclassName = [className stringByAppendingString:@"-Aspects_"]; subclass = objc_allocateClassPair(baseClass, subclassName.UTF8String, 0); // ... add methods ... // update object. objc_registerClassPair(subclass); object_setClass(self, subclass);
  20. Calling Blocks

  21. Calling Blocks // Want we have: [UIViewController aspect_hookSelector:@selector(viewWillAppear:) usingBlock:^(id object,

    NSArray *arguments) { NSLog(@"%@, animated: %tu", object, [arguments.firstObject boolValue]); }]; // Want we want: [UIViewController aspect_hookSelector:@selector(viewWillAppear:) usingBlock:^(id object, BOOL animated) { NSLog(@"%@, animated: %tu", object, animated); }];
  22. Calling blocks via NSInvocation

  23. Calling blocks via NSInvocation 1) NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:blockSignature];

    2) [blockInvocation setArgument:&info atIndex:1]; 3) void *argBuf = NULL; for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) { const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx]; NSUInteger argSize; NSGetSizeAndAlignment(type, &argSize, NULL); argBuf = reallocf(argBuf, argSize); [originalInvocation getArgument:argBuf atIndex:idx]; [blockInvocation setArgument:argBuf atIndex:idx]; } 4) [blockInvocation invokeWithTarget:block];
  24. blockSignature !?

  25. http://clang.llvm.org/docs/Block-ABI-Apple.html typedef NS_OPTIONS(int, AspectBlockFlags) { AspectBlockFlagsHasCopyDisposeHelpers = 1 << 25,

    AspectBlockFlagsHasSignature = 1 << 30 }; typedef struct _AspectBlock { __unused Class isa; AspectBlockFlags flags; __unused int reserved; void (__unused *invoke)(struct _AspectBlock *block, ...); struct { unsigned long int reserved, size; // requires AspectBlockFlagsHasCopyDisposeHelpers void (*copy)(void *dst, const void *src); void (*dispose)(const void *); // requires AspectBlockFlagsHasSignature const char *signature; const char *layout; } *descriptor; // imported variables } *AspectBlockRef;
  26. Comparing Type Signatures // First index is the id<AspectInfo> argument.

    for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) { const char *methodType = [methodSignature getArgumentTypeAtIndex:idx]; const char *blockType = [blockSignature getArgumentTypeAtIndex:idx]; // Only compare parameter, not the optional type data. if (!methodType || !blockType || methodType[0] != blockType[0]) { signaturesMatch = NO; break; } }
  27. Without copying return value [PSPDFDrawView aspect_hookSelector:@selector(shouldProcessTouches:withEvent:) withOptions:AspectPositionInstead usingBlock:^void(id<AspectInfo> info, NSSet

    *touches, UIEvent *e) { BOOL processTouches; NSInvocation *invocation = info.originalInvocation; [invocation invoke]; [invocation getReturnValue:&processTouches]; if (processTouches) { processTouches = pspdf_stylusShouldProcessTouches(touches, e); [invocation setReturnValue:&processTouches]; } }];
  28. With copying return value [PSPDFDrawView aspect_hookSelector:@selector(shouldProcessTouches:withEvent:) withOptions:AspectPositionInstead usingBlock:^BOOL(id<AspectInfo> info, NSSet

    *touches, UIEvent *e) { BOOL processTouches; NSInvocation *invocation = info.originalInvocation; [invocation invoke]; [invocation getReturnValue:&processTouches]; return processTouches && pspdf_stylusShouldProcessTouches(touches, e); }];
  29. Deep into the rabbit hole

  30. "Infinite Loop when overriding a method at two points which

    includes a call to super." - Dave DeLong, Apple
  31. @implementation A - (void)foo {} @end @implementation B - (void)foo

    { [super foo]; } @end [B aspect_hookSelector:@selector(foo) withOptions:0 withBlock:^{ NSLog(@"after -[B foo]"); }]; [A aspect_hookSelector:@selector(foo) withOptions:0 withBlock:^{ NSLog(@"after -[A foo]"); }]; B *b = [[B alloc] init]; [b foo]; // looping...
  32. "I’m pretty sure this is unsolvable without custom assembly or

    private API" - Dave DeLong, Apple
  33. Trampolines

  34. Trampolines We have trampolines on iOS already! IMP imp_implementationWithBlock(void *block);

    void *imp_getBlock(IMP anImp); BOOL imp_removeBlock(IMP anImp);
  35. Trampolines How you would usually do it... mprotect(p, 1024, PROT_WRITE

    | PROT_EXEC);
  36. None
  37. Trampolines imp_implementationForwardingToSelector IMP impl = imp_implementationForwardingToSelector(@selector(setCenter:), NO); class_addMethod(UIView.class, @selector(customSetCenter:), impl,

    typeEncoding);
  38. Trampolines How can we work around w^x?

  39. Trampoline for arm64 _aspects_forwarding_trampoline: sub x12, lr, #0x8 // x12

    = lr - 8 sub x12, x12, #0x4000 // x12 = x12 - 16384 mov lr, x13 // restore the link register ldr x1, [x12] // load x1, which holds SEL _cmd, from *(x12) ldr x12, msgSend // address of objc_msgSend at start of data page br x12 // branch directly to x12 aka objc_msgSend # We repeat this a lot... mov x13, lr bl _aspects_forwarding_trampoline;
  40. Future • x86_64 • Even more tests • KVO •

    New Relic
  41. Tak! Questions? Web links: Aspects: https://github.com/steipete/Aspects Implementing imp_implementationWithBlock(): http://landonf.bikemonkey.org/2011/04/index.html More

    about imp_implementationWithBlock(): http://www.friday.com/bbum/2011/03/17/ios-4-3-imp_implementationwithblock/ imp_implementationForwardingToSelector(): https://github.com/OliverLetterer/imp_implementationForwardingToSelector MABlockClosure: https://github.com/mikeash/MABlockClosure Block-ABI: http://clang.llvm.org/docs/Block-ABI-Apple.html Block Invocation: https://github.com/zwaldowski/BlocksKit/blob/master/BlocksKit/DynamicDelegate/A2BlockInvocation.m Dave DeLong Tweet: https://twitter.com/davedelong/status/462834335528124417 mprotect: https://gist.github.com/stuartcarnie/855607 Image by Flickr, Creative Commons: Intro: https://www.flickr.com/photos/shujimoriwaki/13510752444 Alice: https://www.flickr.com/photos/dannypigart/114365270 Magic: https://www.flickr.com/photos/kt/120468504/ Swizzle: https://www.flickr.com/photos/19779889@N00/7550987452 Blocks: https://www.flickr.com/photos/realsmiley/4895250473 Trampoline: https://www.flickr.com/photos/lintmachine/2937337584 Explosion: https://www.flickr.com/photos/-cavin-/2313239884 Danger: https://www.flickr.com/photos/rhysasplundh/4807749216 Pair: https://www.flickr.com/photos/sporkist/157543688 Apples: https://www.flickr.com/photos/salinaspoet/2062639325 Infinite Loop: https://www.flickr.com/photos/livenature/8064660509 Signature: https://www.flickr.com/photos/wiertz/4563720850 Future: http://www.unleashthefanboy.com/movies/great-scott-back-to-the-future-getting-musical-treatment/88276