$30 off During Our Annual Pro Sale. View Details »

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
PRO

May 19, 2014
Tweet

More Decks by Peter Steinberger

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

  3. 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;

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  9. Here be magic!

    View Slide

  10. _objc_msgForward

    View Slide

  11. _objc_msgForward
    _objc_msgForward_stret

    View Slide

  12. Apple DTS to the rescue?

    View Slide

  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"

    View Slide

  14. Swizzle all the things!

    View Slide

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

    View Slide

  16. View Slide

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

    View Slide

  18. Dynamic Subclassing

    View Slide

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

    View Slide

  20. Calling Blocks

    View Slide

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

    View Slide

  22. Calling blocks via NSInvocation

    View Slide

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

    View Slide

  24. blockSignature !?

    View Slide

  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;

    View Slide

  26. 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;
    }
    }

    View Slide

  27. 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];
    }
    }];

    View Slide

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

    View Slide

  29. Deep into the rabbit hole

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  33. Trampolines

    View Slide

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

    View Slide

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

    View Slide

  36. View Slide

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

    View Slide

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

    View Slide

  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;

    View Slide

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

    View Slide

  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

    View Slide