Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Aspects A delightful, simple library for aspect oriented programming. - (id)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error;

Slide 3

Slide 3 text

Aspects typedef NS_OPTIONS(NSUInteger, AspectOptions) { AspectPositionAfter = 0, AspectPositionInstead = 1, AspectPositionBefore = 2, AspectOptionAutomaticRemoval = 1 << 3, AspectOptionPreferClassMethod = 1 << 4 }; - (id)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error;

Slide 4

Slide 4 text

Debugging [_singleTapGesture aspect_hookSelector:@selector(setState:) withOptions:AspectPositionAfter usingBlock:^(id aspectInfo) { NSLog(@"%@: %@", aspectInfo.instance, aspectInfo.arguments); }];

Slide 5

Slide 5 text

View Controller hooks - (void)pspdf_addWillDismissAction:(void (^)(void))action { [self aspect_hookSelector:@selector(viewWillDisappear:) withOptions:AspectPositionAfter usingBlock:^(id aspectInfo) { if ([aspectInfo.instance isBeingDismissed]) { action(); } }]; }

Slide 6

Slide 6 text

Testing [WebServiceClient aspect_hookSelector:@selector(sharedInstance) withOptions:AspectPositionInstead usingBlock:^id { return mockedClient; }];

Slide 7

Slide 7 text

Analytics [ARAnalytics setupWithConfiguration: @{ ARAnalyticsTrackedScreens: @[ @{ ARAnalyticsClass: UIViewController.class, ARAnalyticsDetails: @[ @{ ARAnalyticsDetails: @[ @{ ARAnalyticsEventName: @"button pressed", ARAnalyticsSelectorName: @"buttonPressed:"), }] }],

Slide 8

Slide 8 text

Message forwarding 1) (id)forwardingTargetForSelector:(SEL)aSelector 2) (NSMethodSignature *)methodSignatureForSelector:(SEL)s 3) (void)forwardInvocation:(NSInvocation *)anInvocation

Slide 9

Slide 9 text

Here be magic!

Slide 10

Slide 10 text

_objc_msgForward

Slide 11

Slide 11 text

_objc_msgForward _objc_msgForward_stret

Slide 12

Slide 12 text

Apple DTS to the rescue?

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Swizzle all the things!

Slide 15

Slide 15 text

Swizzle all the things! - (void)forwardInvocation:(NSInvocation *)invocation; + (Class)class; - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

forwardInvocation: void __ASPECTS_ARE_BEING_CALLED__(NSObject *self, SEL selector, NSInvocation *invocation);

Slide 18

Slide 18 text

Dynamic Subclassing

Slide 19

Slide 19 text

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);

Slide 20

Slide 20 text

Calling Blocks

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Calling blocks via NSInvocation

Slide 23

Slide 23 text

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];

Slide 24

Slide 24 text

blockSignature !?

Slide 25

Slide 25 text

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;

Slide 26

Slide 26 text

Comparing Type Signatures // First index is the id 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; } }

Slide 27

Slide 27 text

Without copying return value [PSPDFDrawView aspect_hookSelector:@selector(shouldProcessTouches:withEvent:) withOptions:AspectPositionInstead usingBlock:^void(id 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]; } }];

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Deep into the rabbit hole

Slide 30

Slide 30 text

"Infinite Loop when overriding a method at two points which includes a call to super." - Dave DeLong, Apple

Slide 31

Slide 31 text

@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...

Slide 32

Slide 32 text

"I’m pretty sure this is unsolvable without custom assembly or private API" - Dave DeLong, Apple

Slide 33

Slide 33 text

Trampolines

Slide 34

Slide 34 text

Trampolines We have trampolines on iOS already! IMP imp_implementationWithBlock(void *block); void *imp_getBlock(IMP anImp); BOOL imp_removeBlock(IMP anImp);

Slide 35

Slide 35 text

Trampolines How you would usually do it... mprotect(p, 1024, PROT_WRITE | PROT_EXEC);

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

Trampolines imp_implementationForwardingToSelector IMP impl = imp_implementationForwardingToSelector(@selector(setCenter:), NO); class_addMethod(UIView.class, @selector(customSetCenter:), impl, typeEncoding);

Slide 38

Slide 38 text

Trampolines How can we work around w^x?

Slide 39

Slide 39 text

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;

Slide 40

Slide 40 text

Future • x86_64 • Even more tests • KVO • New Relic

Slide 41

Slide 41 text

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