Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

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.

Peter Steinberger

May 19, 2014
Tweet

More Decks by Peter Steinberger

Other Decks in Technology

Transcript

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

    (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error;
  2. 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;
  3. Analytics [ARAnalytics setupWithConfiguration: @{ ARAnalyticsTrackedScreens: @[ @{ ARAnalyticsClass: UIViewController.class, ARAnalyticsDetails:

    @[ @{ ARAnalyticsDetails: @[ @{ ARAnalyticsEventName: @"button pressed", ARAnalyticsSelectorName: @"buttonPressed:"), }] }],
  4. 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);
  5. 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); }];
  6. 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];
  7. 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;
  8. 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; } }
  9. 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]; } }];
  10. 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); }];
  11. "Infinite Loop when overriding a method at two points which

    includes a call to super." - Dave DeLong, Apple
  12. @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...
  13. Trampolines We have trampolines on iOS already! IMP imp_implementationWithBlock(void *block);

    void *imp_getBlock(IMP anImp); BOOL imp_removeBlock(IMP anImp);
  14. 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;
  15. 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