Dependency Injection in iOS
-
Upload
pablo-villar -
Category
Software
-
view
202 -
download
4
Transcript of Dependency Injection in iOS
Dependency Injectionin 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
DI concept
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
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)
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.
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.
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
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
What is Dependency Injection?
Let’s see a real world example (an Inaka world example)…
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
What is Dependency Injection?
Our tester will be Demian (couldn’t be other way…)
Evaluates
The testerThe guy
under test
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.
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…?
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
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
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.
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
What is Dependency Injection?
This way of working leads us to develop loosely-coupled designs
CollaboratorCreation
Injection
Class
Class
Interaction
What is Dependency Injection?
This way of working leads us to develop loosely-coupled designs
WITHOUT DI WITH DI
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.
SOLID
Single Responsibility
Interface Segregation
Liskov Substitution
Dependency Inversion
Open / Closed
Some principles that DI will encourage us to follow:
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.
DI relevant concepts
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)
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
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
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.
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.
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
Seams
Class
Volatiledependency
Class
Volatiledependency
Seam
BEFORE AFTER
Seams
CounterViewController
Counter
CounterViewController
Counter
Seam
BEFORE AFTER
Seams
CounterViewController
Counter
CounterViewController
Counter
Seam
BEFORE AFTER
replaceable interchangeable
Seams
CounterViewController
Counter
CounterViewController
Counter
Seam
BEFORE AFTER
FakeCounter
Seams
CounterViewController
Counter
CounterViewController
Counter
Seam
BEFORE AFTER
Good for unit testing!
FakeCounter
… and for loose-coupling!
Applying DI
Methods to apply DI:
Extract and override
Method injection
Setter injection (a.k.a. Property injection)
Constructor injection
Extract and override
volatile dependency detected
Extract and override
extract
Extract and override
Subclasses can override this method and provide whatever manager they want.
extract
* Observe that [AFHTTPRequestOperationManager manager] has become our default manager.
Extract and override
Extract and override
Subclass
… or whatever you want
Extract and override
Subclass
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
Method injection
Method injection
We inject the dependency from the outside
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
Method injection
Method injection
Method injection in action
DRMessagesFetcher.h
DRMessagesFetcher.m
Setter injection
DRMessagesFetcher.h
DRMessagesFetcher.m
Setter injection
We inject the dependency via a property
Setter injection
We can make a trick to have a default dependency initialization and make
the injection optional.
Setter injection
Setter injection
Setter injection in action
Setter injection
Constructor injection
This way we make the property private
Constructor injection
This way we make the property private
Constructor injection
We inject our dependency from the outside through a constructor
Constructor injection
Default initialization
Constructor injection
Default initialization
Custom initialization
Constructor injection
Default initialization
Custom initialization
When testing, we will use this one
Constructor injection
Constructor injection
Constructor injection in action
Constructor injection
TDD+DI example (in iOS)
Let’s make a very simple application from scratch by applying TDD and some DI
increment
Count:
Yet Another Counter
0
Let’s make a very simple application from scratch by applying TDD and some DI
increment
Count:
Yet Another Counter
0123
But first,
let’s remember which kind of tests we categorized last time…
3 kind of verifications
(a.k.a. Interaction Verification)
Counter ViewController
View-Controller class
Counter
Model class
This is what we start with…
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
State verification tests
Counter.h
Counter.m
CounterTests.m
Counter.h
Counter.m
CounterTests.m
State verification tests
increment
View-Controller class
Model class
Let’s add some interaction…
Counter ViewController Counter
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
Interaction test
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
Interaction test
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
Interaction test
Counter.h
Counter.m
CounterTests.m
It remains all the same
so far…
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
increment
didIncrement
View-Controller class
Model class
Let’s add a feedback interaction…
Counter ViewController Counter
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
Counter.h
Counter.m
CounterTests.m
Counter.h
Counter.m
CounterTests.m
Counter.h
Counter.m
CounterTests.m
Interaction test
Counter.h
Counter.m
CounterTests.m
Interaction test
Counter.h
Counter.m
CounterTests.m
Interaction test
Counter.h
Counter.m
CounterTests.m
Interaction test
“did increment” before incrementing???
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];
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.
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.
Counter.h
Counter.m
CounterTests.m
Counter.h
Counter.m
CounterTests.m
Counter.h
Counter.m
CounterTests.m
Counter.h
Counter.m
CounterTests.m
increment
didIncrement
View-Controller class
Model class
Almost there…
Just don’t forget to update the screen…!
Counter ViewController Counter
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
State verification test
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
State verification test
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
State verification test
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
State verification testThis ain’t updating,
is it???
CounterViewController.h
CounterViewController.m
CounterViewControllerTests.m
State verification testThere ya go!
increment
didIncrement
View-Controller class
Model class
We are there.
Finally, we have our application working!
Counter ViewController Counter
TDD+DI real case (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
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
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
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
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
DI: Pros & Cons
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.
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.”
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 !
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
References
Dependency Injection
by Mark Seemann
in .NET
2012 .NET
The Art Of Unit Testing
by Roy Osherove
with Examples in .NET
2009 .NET
Working Effectively With Legacy Code
by Michael Feathers
2005 Java, C++, C
/Dependency_injection
Wikipedia
/Inversion_of_control
/SOLID_(object-oriented_design)
/Test-driven_development
http://en.wikipedia.org/wiki/
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)
That’s all, folks! (this story may continue soon though…)
I hope you enjoy implementing
TDD + DI in iOS
:D
Pablo Villar - May 2014 @ InakaLabs