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

Dependency Injection FTW

0xc010d
February 18, 2014

Dependency Injection FTW

cocoaheadsNL@Utrecht/Atos

0xc010d

February 18, 2014
Tweet

More Decks by 0xc010d

Other Decks in Programming

Transcript

  1. Dependency Injection FTW
    Ievgen Solodovnykov
    @0xc010d

    View Slide

  2. I…
    • work at 2dehands.be
    • do iOS stuff during the daylight

    View Slide

  3. Inversion of Control
    • M. Fowler, 1988 (http://martinfowler.com/bliki/
    InversionOfControl.html)
    • Don't call us, we'll call you
    • Dependency Injection is one of possible
    implementations

    View Slide

  4. Dependency Injection
    • M. Fowler, 2004 (http://www.martinfowler.com/
    articles/injection.html)
    • Helps to get rid of hard coupling
    • Easier unit testing and motivates to write testable
    code
    • Improves modularity and reusability

    View Slide

  5. Dependency Injection
    • Constructor injection
    • Property injection
    • Interface injection
    + Automatic injection

    View Slide

  6. Constructor injection
    @interface APIClient : NSObject
    - (void)loadDataWithCompletionBlock:(void(^)(NSError *))block;
    @end
    !
    //...
    !
    @implementation ViewController {
    APIClient *_apiClient;
    }
    !
    - (instancetype)initWithAPIClient:(APIClient *)apiClient {
    self = [super init];
    _apiClient = apiClient;
    return self;
    }
    !
    - (void)viewDidLoad {
    [super viewDidLoad];
    [_apiClient loadDataWithCompletionBlock:^(NSError *) {
    //...
    }];
    }
    !
    @end

    View Slide

  7. Property injection
    @interface ViewController : UIViewController
    !
    @property (nonatomic, weak) APIClient *apiClient;
    !
    @end
    !
    //...
    !
    @implementation ViewController
    !
    - (void)viewDidLoad {
    [super viewDidLoad];
    [self.apiClient loadDataWithCompletionBlock:^(NSError *) {
    //...
    }];
    }
    !
    @end

    View Slide

  8. Method injection
    @implementation ViewController
    !
    - (void)viewDidLoad {
    [super viewDidLoad];
    [self loadDataWithAPIClient:[[APIClient alloc] init]];
    }
    !
    - (void)loadDataWithAPIClient:(APIClient *)apiClient {
    [apiClient loadDataWithCompletionBlock:^(NSError *) {
    //...
    }];
    }
    !
    @end

    View Slide

  9. Automatic injection with
    BloodMagic
    • https://github.com/railsware/BloodMagic
    • Runtime-based
    • Less code
    • Custom initializers
    • And more…

    View Slide

  10. BMLazy
    @interface ViewController ()
    !
    @property (nonatomic, weak) APIClient *apiClient;
    !
    @end
    !
    !
    @implementation ViewController
    !
    @dynamic apiClient;
    !
    - (void)viewDidLoad {
    [super viewDidLoad];
    [self.apiClient loadDataWithCompletionBlock:^(NSError *) {
    //...
    }];
    }
    !
    @end

    View Slide

  11. BMLazy
    [[APIClient alloc] init]
    @interface ViewController ()
    !
    @property (nonatomic, weak) APIClient *apiClient;
    !
    @end
    !
    !
    @implementation ViewController
    !
    @dynamic apiClient;
    !
    - (void)viewDidLoad {
    [super viewDidLoad];
    [self.apiClient loadDataWithCompletionBlock:^(NSError *) {
    //...
    }];
    }
    !
    @end

    View Slide

  12. Custom initializer
    __attribute__((constructor)) void configureAPIClient(void) {
    BMInitializer *initializer = [BMInitializer lazyInitializer];
    initializer.propertyClass = [APIClient class];
    initializer.initializer = ^id (id sender) {
    static dispatch_once_t onceToken;
    static APIClient *client;
    dispatch_once(&onceToken, ^{
    client = [[APIClient alloc] init];
    });
    return client;
    };
    [initializer registerInitializer];
    }

    View Slide

  13. Custom initializer
    {
    static dispatch_once_t onceToken;
    static APIClient *client;
    dispatch_once(&onceToken, ^{
    client = [[APIClient alloc] init];
    });
    return client;
    };
    @interface ViewController ()
    !
    @property (nonatomic, weak) APIClient *apiClient;
    !
    @end
    !
    !
    @implementation ViewController
    !
    @dynamic apiClient;
    !
    - (void)viewDidLoad {
    [super viewDidLoad];
    [self.apiClient loadDataWithCompletionBlock:^(NSError *) {
    //...
    }];
    }
    !
    @end

    View Slide

  14. Protocol binding
    @protocol APICLient
    !
    - (void)loadDataWithCompletionBlock:(void (^)(NSError *))completionBlock;
    !
    @end
    !
    //---
    !
    @interface ProductionAPIClient : NSObject @end
    !
    //---
    !
    @interface ViewController ()
    !
    @property (nonatomic, weak) id apiClient;
    !
    @end
    !
    //---
    !
    @dynamic apiClient;
    !
    - (void)viewDidLoad {
    [super viewDidLoad];
    [self.apiClient loadDataWithCompletionBlock:^(NSError *) {
    //...
    }];
    }

    View Slide

  15. Protocol initializer
    __attribute__((constructor)) void configureProductionAPIClient(void) {
    BMInitializer *initializer = [BMInitializer lazyInitializer];
    initializer.protocols = @[ @protocol(APICLient) ];
    initializer.initializer = ^id (id sender){
    static dispatch_once_t onceToken;
    static ProductionAPIClient *client;
    dispatch_once(&onceToken, ^{
    client = [[ProductionAPIClient alloc] init];
    });
    return client;
    };
    [initializer registerInitializer];
    }
    !

    View Slide

  16. Container initializer
    @interface DemoViewController : ViewController @end
    !
    @interface DemoAPIClient : NSObject @end
    !
    __attribute__((constructor)) void configureDemoAPIClient(void) {
    BMInitializer *initializer = [BMInitializer lazyInitializer];
    initializer.protocols = @[ @protocol(APICLient) ];
    initializer.containerClass = [DemoViewController class];
    initializer.initializer = ^id (id sender){
    static dispatch_once_t onceToken;
    static DemoAPIClient *client;
    dispatch_once(&onceToken, ^{
    client = [[DemoAPIClient alloc] init];
    });
    return client;
    };
    [initializer registerInitializer];
    }

    View Slide

  17. Disadvantages
    • Runtime injecting requires time
    • Runtime errors
    • Requires some intelligence

    View Slide

  18. Alternatives
    • Typhoon (http://www.typhoonframework.org)
    • Objection (http://objection-framework.org)
    • Xider a.k.a LightMagic (@0xc010d)

    View Slide

  19. Xider vs BloodMagic
    • Almost as fast as ivar assignment
    • Less flexible implementation :(

    View Slide

  20. Unit testing
    / with BloodMagic

    View Slide

  21. Return value tests
    • Works fine with functions, often doesn’t require
    injection
    • Call function/method with different parameters
    • Assert return value

    View Slide

  22. State tests
    • Might require injection
    • Setup object
    • Call method
    • Assert object property / other method return
    value

    View Slide

  23. Interaction tests
    • Most often requires injection
    • Setup object
    • Call method
    • Assert other object property / method return
    value

    View Slide

  24. Demo

    View Slide

  25. Thank you!
    https://speakerdeck.com/0xc010d/dependency-injection-ftw
    @0xc010d

    View Slide