Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. 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
  2. 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
  3. 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
  4. Property injection @interface ViewController : UIViewController ! @property (nonatomic, weak)

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

    [self loadDataWithAPIClient:[[APIClient alloc] init]]; } ! - (void)loadDataWithAPIClient:(APIClient *)apiClient { [apiClient loadDataWithCompletionBlock:^(NSError *) { //... }]; } ! @end
  6. BMLazy @interface ViewController () <BMLazy> ! @property (nonatomic, weak) APIClient

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

    (nonatomic, weak) APIClient *apiClient; ! @end ! ! @implementation ViewController ! @dynamic apiClient; ! - (void)viewDidLoad { [super viewDidLoad]; [self.apiClient loadDataWithCompletionBlock:^(NSError *) { //... }]; } ! @end
  8. 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]; }
  9. Custom initializer { static dispatch_once_t onceToken; static APIClient *client; dispatch_once(&onceToken,

    ^{ client = [[APIClient alloc] init]; }); return client; }; @interface ViewController () <BMLazy> ! @property (nonatomic, weak) APIClient *apiClient; ! @end ! ! @implementation ViewController ! @dynamic apiClient; ! - (void)viewDidLoad { [super viewDidLoad]; [self.apiClient loadDataWithCompletionBlock:^(NSError *) { //... }]; } ! @end
  10. Protocol binding @protocol APICLient <NSObject> ! - (void)loadDataWithCompletionBlock:(void (^)(NSError *))completionBlock;

    ! @end ! //--- ! @interface ProductionAPIClient : NSObject <APICLient> @end ! //--- ! @interface ViewController () <BMLazy> ! @property (nonatomic, weak) id<APICLient> apiClient; ! @end ! //--- ! @dynamic apiClient; ! - (void)viewDidLoad { [super viewDidLoad]; [self.apiClient loadDataWithCompletionBlock:^(NSError *) { //... }]; }
  11. 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]; } !
  12. Container initializer @interface DemoViewController : ViewController @end ! @interface DemoAPIClient

    : NSObject <APICLient> @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]; }
  13. Xider vs BloodMagic • Almost as fast as ivar assignment

    • Less flexible implementation :(
  14. Return value tests • Works fine with functions, often doesn’t

    require injection • Call function/method with different parameters • Assert return value
  15. State tests • Might require injection • Setup object •

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

    • Call method • Assert other object property / method return value