Dependency Injection in iOS

124
Dependency Injection in iOS

Transcript of Dependency Injection in iOS

Page 1: Dependency Injection in iOS

Dependency Injectionin iOS

Page 2: Dependency Injection in iOS

I’m going to talk about…

• DI concept

• DI relevant concepts

• How to apply DI

• TDD+DI example (in iOS)

• TDD+DI real case (in iOS)

• DI Pros & Cons

• References

Page 3: Dependency Injection in iOS

DI concept

Page 4: Dependency Injection in iOS

What is Dependency Injection?

Dependency Injection (DI) is a software design pattern that implements inversion of control (IoC) and

allows a program design to follow the dependency inversion principle.

- Wikipedia

Page 5: Dependency Injection in iOS

What is Dependency Injection?

Dependency Injection (DI) is a set of software design principles and patterns that enables us to develop

loosely coupled code.

- Mark Seemann (Dependency Injection in .NET)

Page 6: Dependency Injection in iOS

What is Dependency Injection?

The basic concept is that we separate dependencies from classes that uses them.

But… What is a dependency?

A dependency is an object that your class under test interacts with.

Page 7: Dependency Injection in iOS

What is Dependency Injection?

CollaboratorSUT

DependencyClass

Interaction

A dependency is a collaborator, is someone that your

class under test needs to use for some reason.

Page 8: Dependency Injection in iOS

What is Dependency Injection?

The problem is that we use to create dependencies from within the SUT

The base of DI is that we should create dependencies from

somewhere else, not within our SUT

CollaboratorSUT

DependencyClass

Instantiation

Interaction

CollaboratorSUT

DependencyClass

Interaction Instantiation Somewhere else

Another class

Page 9: Dependency Injection in iOS

What is Dependency Injection?

The idea of DI is that somehow we will inject those dependencies into our SUT…

Collaborator

Dependency

SUT

Class

Interaction

Instantiation

Somewhere else

Another class

Injection

This separates construction from behavior

Page 10: Dependency Injection in iOS

What is Dependency Injection?

Let’s see a real world example (an Inaka world example)…

Page 11: Dependency Injection in iOS

What is Dependency Injection?

Let’s see a real world example (an Inaka world example)…

Say we are testing Nacho, say we want to see whether he has a good working performance, i.e. whether he is producing as we expect.

Guy under test

Page 12: Dependency Injection in iOS

What is Dependency Injection?

Our tester will be Demian (couldn’t be other way…)

Evaluates

The testerThe guy

under test

Page 13: Dependency Injection in iOS

What is Dependency Injection?

But, let’s say Nacho needs Gera’s help to do his work…

Evaluates Needs

The testerThe guy

under testThe

dependency

So, Nacho is depending upon Gera to have his job done.

There is some task that Nacho needs from Gera to be done to complete his work.

Page 14: Dependency Injection in iOS

What is Dependency Injection?

Even though Gera is a hard-working guy,

he is human, so it will take some time and effort to complete the work he has to do for

Nacho.

But, wait… We are not testing Gera’s performance, but Nacho’s.

In other words, as the guy under test is

Nacho, we should not depend upon Gera’s work to see how Nacho works.

So, what to do…?

Page 15: Dependency Injection in iOS

What is Dependency Injection?

Well, Demian (the tester) can make up some sort of fake Gera, one

that will have everything there for Nacho as soon as he needs it.

Imagine this fake Gera as some sort of terminator that will have

everything Nacho needs immediately and perfectly done for him.

Creates

The tester

The dependency

fake Gera

Page 16: Dependency Injection in iOS

What is Dependency Injection?

Creates

The tester

The dependency

Passes it to

The guy under test

Nacho needs a Gera to help him, and it’s Demian who’s telling him: “Hey, take this Gera and use this one”

Interacts with

fake Gera

Page 17: Dependency Injection in iOS

What is Dependency Injection?

At this point, we see that it’s not up to Nacho to choose WHAT Gera to use, all he needs to know is that he’s going to use some sort

of Gera to help him.

It’s another guy who is choosing WHAT Gera to use, that guy is

Demian.

This way we assure that we are testing Nacho, no matter what Gera

he uses, as long as he uses one.

Page 18: Dependency Injection in iOS

Collaborator

What is Dependency Injection?

Let’s see the equivallences of this example and the OO world…

Creates

The testerThe

dependency

Passes it to

The guy under test

Instantiates

The testerThe

dependency

Injects

The system under test

Test class

SUT

Note that “passing the dependency to” is referred to as “injecting” the dependency.

Interacts with

Interacts with

fake Gera

Page 19: Dependency Injection in iOS

What is Dependency Injection?

This way of working leads us to develop loosely-coupled designs

CollaboratorCreation

Injection

Class

Class

Interaction

Page 20: Dependency Injection in iOS

What is Dependency Injection?

This way of working leads us to develop loosely-coupled designs

WITHOUT DI WITH DI

Page 21: Dependency Injection in iOS

What is Dependency Injection for?

DI’s overall purpose is code maintainability.

By writing maintainable code, we will spend less time finding and fixing bugs.

One of many ways to write maintainable code is through loose coupling.

Page 22: Dependency Injection in iOS

SOLID

Single Responsibility

Interface Segregation

Liskov Substitution

Dependency Inversion

Open / Closed

Some principles that DI will encourage us to follow:

Page 23: Dependency Injection in iOS

SOLID

ingle Responsibility

pen / Closed

iskov Substitution

nterface Segregation

ependency Inversion

A class should have only one single responsibility.

Classes should be open for extension, but closed for modification.

Objects in a program should be replaceable with other objects of the same interface without breaking either client or implementation.

Many client-specific interfaces are better than one general-purpose interface.

Depend upon abstractions. Do not depend upon concretions.

Page 24: Dependency Injection in iOS

DI relevant concepts

Page 25: Dependency Injection in iOS

Unit tests provide rapid feedback on the state of an application.

However, it’s only possible to write unit tests when the unit in question can be properly isolated from its

dependencies.

TDD is the safest way to ensure testability.

(a software is considered testable when it’s susceptible to unit testing)

Page 26: Dependency Injection in iOS

That’s the point in which TDD is related to DI

We use DI to isolate classes from their dependencies

TDD leverages unit tests, which need our classes to be

isolated from their dependencies

Page 27: Dependency Injection in iOS

Stable Dependencies -vs- Volatile Dependencies

You expect that new versions won’t contain breaking changes

Class already exists

Types in question contain deterministic algorythms

You never expect to have to replace the class with another

Example: UITextField

The class introduces a requirement to setup and

configure runtime envionment

The class contains nondeterministic behavior

Important in unit tests: All tests should be deterministic

Example: AFNetworking

The class doesn’t yet exist, but it’s still in development

Page 28: Dependency Injection in iOS

Volatile Dependencies are the focal point of DI

When we have a volatile dependency, we must introduce a seam in our class.

A seam is sort of a gate,

from which we are going to relate a class with its dependency.

We can see seam as the separation between the class and its dependency.

Page 29: Dependency Injection in iOS

Seams

Everywhere we decide to program against an interface instead of a concrete type, we introduce a seam.

A seam is a place where an application is assembled from its constituent parts.

This is similar to the way a piece of clothing is sewn together at its seams.

A seam is also a place where we can disassemble the application and work with their modules in isolation.

Page 30: Dependency Injection in iOS

We won’t lose the control of the dependency, though. We’ll just move it to another place.

This is an application of the Single Responsibility Principle, because our classes would only deal with their given area

of responsibility.

This means we will make the class give up control of the dependency (hence, its lifetime).

Seams

Page 31: Dependency Injection in iOS

Seams

Class

Volatiledependency

Class

Volatiledependency

Seam

BEFORE AFTER

Page 32: Dependency Injection in iOS

Seams

CounterViewController

Counter

CounterViewController

Counter

Seam

BEFORE AFTER

Page 33: Dependency Injection in iOS

Seams

CounterViewController

Counter

CounterViewController

Counter

Seam

BEFORE AFTER

replaceable interchangeable

Page 34: Dependency Injection in iOS

Seams

CounterViewController

Counter

CounterViewController

Counter

Seam

BEFORE AFTER

FakeCounter

Page 35: Dependency Injection in iOS

Seams

CounterViewController

Counter

CounterViewController

Counter

Seam

BEFORE AFTER

Good for unit testing!

FakeCounter

… and for loose-coupling!

Page 36: Dependency Injection in iOS

Applying DI

Page 37: Dependency Injection in iOS

Methods to apply DI:

Extract and override

Method injection

Setter injection (a.k.a. Property injection)

Constructor injection

Page 38: Dependency Injection in iOS

Extract and override

Page 39: Dependency Injection in iOS

volatile dependency detected

Extract and override

Page 40: Dependency Injection in iOS

extract

Extract and override

Page 41: Dependency Injection in iOS

Subclasses can override this method and provide whatever manager they want.

extract

* Observe that [AFHTTPRequestOperationManager manager] has become our default manager.

Extract and override

Page 42: Dependency Injection in iOS

Extract and override

Subclass

Page 43: Dependency Injection in iOS

… or whatever you want

Extract and override

Subclass

Page 44: Dependency Injection in iOS

Here (in a subclass) you can provide whatever manager you want.

This allows testing classes to replace real dependency objects with fakes.

In this case, you must use your testable version of the class (i.e. your subclass) to test.

… or whatever you want

Extract and override

Subclass

Page 45: Dependency Injection in iOS

Method injection

Page 46: Dependency Injection in iOS

Method injection

We inject the dependency from the outside

Page 47: Dependency Injection in iOS

This allows us to inject whatever instance we need at any moment we need to use the method.

Usually, we will inject a fake object when testing the method…

Method injection

We inject the dependency from the outside

Page 48: Dependency Injection in iOS

Method injection

Page 49: Dependency Injection in iOS

Method injection

Method injection in action

Page 50: Dependency Injection in iOS

DRMessagesFetcher.h

DRMessagesFetcher.m

Setter injection

Page 51: Dependency Injection in iOS

DRMessagesFetcher.h

DRMessagesFetcher.m

Setter injection

We inject the dependency via a property

Page 52: Dependency Injection in iOS

Setter injection

Page 53: Dependency Injection in iOS

We can make a trick to have a default dependency initialization and make

the injection optional.

Setter injection

Page 54: Dependency Injection in iOS

Setter injection

Page 55: Dependency Injection in iOS

Setter injection in action

Setter injection

Page 56: Dependency Injection in iOS

Constructor injection

Page 57: Dependency Injection in iOS

This way we make the property private

Constructor injection

Page 58: Dependency Injection in iOS

This way we make the property private

Constructor injection

We inject our dependency from the outside through a constructor

Page 59: Dependency Injection in iOS

Constructor injection

Page 60: Dependency Injection in iOS

Default initialization

Constructor injection

Page 61: Dependency Injection in iOS

Default initialization

Custom initialization

Constructor injection

Page 62: Dependency Injection in iOS

Default initialization

Custom initialization

When testing, we will use this one

Constructor injection

Page 63: Dependency Injection in iOS

Constructor injection

Page 64: Dependency Injection in iOS

Constructor injection in action

Constructor injection

Page 65: Dependency Injection in iOS

TDD+DI example (in iOS)

Page 66: Dependency Injection in iOS

Let’s make a very simple application from scratch by applying TDD and some DI

increment

Count:

Yet Another Counter

0

Page 67: Dependency Injection in iOS

Let’s make a very simple application from scratch by applying TDD and some DI

increment

Count:

Yet Another Counter

0123

Page 68: Dependency Injection in iOS

But first,

let’s remember which kind of tests we categorized last time…

3 kind of verifications

(a.k.a. Interaction Verification)

Page 69: Dependency Injection in iOS

Counter ViewController

View-Controller class

Counter

Model class

This is what we start with…

Page 70: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

Page 71: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

State verification tests

Page 72: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

Page 73: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

State verification tests

Page 74: Dependency Injection in iOS

increment

View-Controller class

Model class

Let’s add some interaction…

Counter ViewController Counter

Page 75: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

Page 76: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

Page 77: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

Interaction test

Page 78: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

Interaction test

Page 79: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

Interaction test

Page 80: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

It remains all the same

so far…

Page 81: Dependency Injection in iOS

Let’s add a trick to default initialize our dependency in our View Controller…

Thus, other classes that need to use CounterViewController don’t have to remember what dependencies to initialize (even though they optionally can) and unnecessarily mess the

code up everywhere.

In CounterViewController.m

default initializer

Page 82: Dependency Injection in iOS

increment

didIncrement

View-Controller class

Model class

Let’s add a feedback interaction…

Counter ViewController Counter

Page 83: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

Page 84: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

Page 85: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

Page 86: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

Interaction test

Page 87: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

Interaction test

Page 88: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

Interaction test

Page 89: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

Interaction test

“did increment” before incrementing???

Page 90: Dependency Injection in iOS

Currently, our

test method is not ensuring that the “did increment” call should be done right after incrementing, and not before.

Having said that, we will know that this test will pass, because it’s only testing that the “did delegate” call is being done, no matter when, as long as we call [counter increment];

Page 91: Dependency Injection in iOS

This is giving us a false sense of correctness.

That’s one of the drawbacks of TDD that we saw in my previous talk about TDD.

Currently, our

test method is not ensuring that the “did increment” call should be done right after incrementing, and not before.

Page 92: Dependency Injection in iOS

So, we will need to modify this test to make sure the “did increment” delegate call is being fired once the integer counter property has been increased.

Currently, our

test method is not ensuring that the “did increment” call should be done right after incrementing, and not before.

Page 93: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

Page 94: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

Page 95: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

Page 96: Dependency Injection in iOS

Counter.h

Counter.m

CounterTests.m

Page 97: Dependency Injection in iOS

increment

didIncrement

View-Controller class

Model class

Almost there…

Just don’t forget to update the screen…!

Counter ViewController Counter

Page 98: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

Page 99: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

State verification test

Page 100: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

State verification test

Page 101: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

State verification test

Page 102: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

State verification testThis ain’t updating,

is it???

Page 103: Dependency Injection in iOS

CounterViewController.h

CounterViewController.m

CounterViewControllerTests.m

State verification testThere ya go!

Page 104: Dependency Injection in iOS

increment

didIncrement

View-Controller class

Model class

We are there.

Finally, we have our application working!

Counter ViewController Counter

Page 105: Dependency Injection in iOS

TDD+DI real case (in iOS)

Page 106: Dependency Injection in iOS

DailyReader

DailyReader is an iOS app that fetches all the messages of the current day from Inaka’s main room and displays them on the screen through a grouped table, categorizing them into three different groups:

‣ “dailies” ‣ “not so descriptive dailies” ‣ “other messages”

It was entirely developed by applying TDD and DI.

https://github.com/inaka/daily-reader

Page 107: Dependency Injection in iOS

DRMessages ViewController

DRMessagesFetcher

AFHTTP OperationManagerDRURLManager DRDataParser

DRMessage

DRMessageCellDRMessages DataSource

via delegate

via blocks

Model Classes

View/Controller Classes

External Classes

References

asks for messages

leverages (to configure request URL) leverages

needs

returns array of messages

returns responses

needs (to set the cell)

leverages (to configure

itself)

createssends array with messages

asks for JSON

DailyReader architecture

Page 108: Dependency Injection in iOS

This is what DailyReader could have looked like if we had not applied TDD+DI

DRMessages ViewController DRManager

AFHTTP OperationManager

DRMessage

via delegate

via blocks

Model Classes

View/Controller Classes

External Classes

References

asks for

messages

needs

returns array of messages

returns responsesasks for

JSON

needs

Page 109: Dependency Injection in iOS

This is what DailyReader could have looked like if we had not applied TDD+DI

DRMessages ViewController DRManager

AFHTTP OperationManager

DRMessage

via delegate

via blocks

Model Classes

View/Controller Classes

External Classes

References

asks for

messages

needs

returns array of messages

returns responsesasks for

JSON

• Fewer classes

• Classes are tightly coupled

• Responsibilites are not clear

• Code is less extensible

• Code is less testable

• Code is less maintainable

What do we see here?

needs

Page 110: Dependency Injection in iOS

This is what DailyReader could have looked like if we had not applied TDD+DI

DRMessages ViewController

AFHTTP OperationManager

DRMessage

via delegate

via blocks

Model Classes

View/Controller Classes

External Classes

References

asks for

messages

needs

returns array of messages

returns responsesasks for

JSON

needs

DRManager would be a God Class

with these responsibilites:• Communicating to

networking classes

• Configuring request URLs

• Parsing responses into model objects

• Creating model objects

It definitely breaks the Single

Responsibility Principle

such a code smell

Page 111: Dependency Injection in iOS

DI: Pros & Cons

Page 112: Dependency Injection in iOS

When we applied TDD+DI, we spent more time and effort on the developing process, but on the other hand, we’ve been rewarded with these great things:

✓ Flexible architecture

✓ Extensible code

✓ Maintainable code

… and, the best one…

✓ We now have tests that will catch us (or any developer who has to modify code) in case we make mistakes when refactoring, bugfixing, or adding new features.

Benefits of applying TDD + DI

… all these things will lead us to spend much less time and effort on future refactorings / bug fixings processes.

Page 113: Dependency Injection in iOS

Remember this?Team progress and output measured with and without tests

- Roy Osherove, The Art Of Unit Testing

“That’s why it’s important to emphasize that, although unit testing can increase the amount of

time it takes to implement a feature, the time balances out over the product’s release cycle because

of increased quality and maintainability.”

Page 114: Dependency Injection in iOS

There are also more benefits of applying dependency injection:

✓Classes are easier to unit-test in isolation, leveraging fake objects.

✓We can externalize system’s configuration details into

configuration files, allowing the system to be reconfigured without

recompilation.

✓ Separated configurations can be written for different situations that

require different implementations of components.

✓DI allows concurrent or independient development.

Even more benefits of applying TDD + DI !

Page 115: Dependency Injection in iOS

But… Be careful: There are some downsides when applying DI…

๏ Code is more difficult to trace (read).

๏ We will usually require more lines of code to accomplish the same behavior legacy code would.

๏ It adds much more time and effort to the process of code development.

Because DI separates behavior from construction

Page 116: Dependency Injection in iOS

References

Page 117: Dependency Injection in iOS

Dependency Injection

by Mark Seemann

in .NET

2012 .NET

Page 118: Dependency Injection in iOS

The Art Of Unit Testing

by Roy Osherove

with Examples in .NET

2009 .NET

Page 119: Dependency Injection in iOS

Working Effectively With Legacy Code

by Michael Feathers

2005 Java, C++, C

Page 120: Dependency Injection in iOS

http://qualitycoding.org/Jon Reid’s blog

2011 - Actuality

Page 122: Dependency Injection in iOS

Other internet articleshttp://codebetter.com/jeremymiller/2005/10/06/the-dependency-injection-pattern-%E2%80%93-what-is-it-and-why-do-i-care/

http://di-objective-c.blogspot.com.ar/2010/06/diy-di-objective-c.html

http://www.slideshare.net/MarcMorera/dependency-injection-29887934

http://blog.8thlight.com/eric-smith/2009/04/16/dependency-inversion-principle-and-iphone.html

https://www.youtube.com/watch?v=_CeWMxzB1SI (eBay Tech Talk with Jon Reid about TDD for iOS)

Page 123: Dependency Injection in iOS

That’s all, folks! (this story may continue soon though…)

Page 124: Dependency Injection in iOS

I hope you enjoy implementing

TDD + DI in iOS

:D

Pablo Villar - May 2014 @ InakaLabs