Dependency Injection
DI is a technique widely used in software programming. Even Google recommends DI to Android devs, Android has libraries like Dagger, Hilt for this purpose. DI provides the following advantages:
- Code Reusability
- Ease of refactoring
- Ease of testing
Fundamental
Dependency injection is a technique that allows objects to be separated from the objects they depend upon. Let's take an example of how to implement DI in iOS.
class HTTPClient {
func downloadFrom(url: URL) {
//download data from remote
}
}
class DataDownload {
let client = HTTPClient()
func getData(url: URL) {
client.downloadFrom(url: url)
}
}
class client {
let dataDownload: DataDownload
init() {
self.dataDownload = DataDownload()
self.dataDownload.getData(url: URL(string: "https://any_http_url.com")!)
}
}
Class
DataDownload
is used for downloading data from given url.HTTPClient
is initiated insideDataDownload
class. Now suppose I need to download data from another source, say FTP source. Let’s create
class FTPClient {
func downloadFrom(url: URL) {
//download data from remote
}
}
class DataDownload {
let client = FTPClient()
func getData(url: URL) {
client.downloadFrom(url: url)
}
}
class client {
let dataDownload: DataDownload
init() {
self.dataDownload = DataDownload()
self.dataDownload.getData(url: URL(string: "https://any_ftp_url.com")!)
}
}
A new
FTPClient
is created along with changes inDataDownload
class, though functionality of this class remains same but still it changes as it is coupled withHTTPClient
. In future our client can change n number of times but modifying ourDataDownload
class is unnecessary resource consumption. Instead we need to decouple DataDownload with client class.
protocol RemoteClient {
func downloadFrom(url: URL)
}
class FTPClient: RemoteClient {
func downloadFrom(url: URL) {
//download data from remote
}
}
class DataDownload {
let client: RemoteClient
init(client: RemoteClient) {
self.client = client
}
func getData(url: URL) {
client.downloadFrom(url: url)
}
}
class Client1 {
let dataDownload: DataDownload
init() {
self.dataDownload = DataDownload(client: FTPClient())
self.dataDownload.getData(url: URL(string: "https://any_ftp_url.com")!)
}
}
class Client2 {
let dataDownload: DataDownload
init() {
self.dataDownload = DataDownload(client: HTTPClient())
self.dataDownload.getData(url: URL(string: "https://any_htttp_url.com")!)
}
}
By adding protocol
RemoteClient
, I can decoupleDataDownload
from concrete implementation ofRemoteClient
. HereClient1
wants to download data from FTP url, it can initaiteFTPClient
and inject it as dependency toDataDownload
class whileClient2
wants to download data from HTTP url, it can initaiteHTTPClient
and inject it toDataDownload
class.
RemoteClient
is injected to DataDownload
from the composer/assembler Client1
and Client2
. It also decouples code. Now we can test them in separation without fulfilling unwanted dependency. Some pros from the above code refactoring
- Ease in testing.
- Quicker development of new features.
- Improved Xcode compile time(I will write a separate blog on this).
- Saves time and money.
Types of DI
- Constructor injection (explained above)
- Function injection
- Property injection
Function injection is injecting dependency in the function’s parameter.
class ChildViewController: UIViewController {
func viewDidLoad() { }
func viewWillAppear() { }
func viewDidAppear() { }
func getDataFrom(_ client: RemoteClient, url: URL) {
client.downloadFrom(url: url)
}
}
class ParentViewController: UIViewController {
func viewDidLoad() { }
func viewWillAppear() { }
func viewDidAppear() { }
func present() {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "ChildViewController") as! ChildViewController
vc.getDataFrom(client: HTTPClient(), url: URL(string:"https://any-url.com")!)
}
}
HTTPClient
is being injected as function’s depedency.ChildViewController
doesn’t know about concrete implementation ofRemoteClient
.ParentViewController
it’s composer knows concrete implementation
Property injection: HTTPClient is passed as Property injection to var client
.
class ChildViewController: UIViewController {
var client: RemoteClient?
func viewDidLoad() { }
func viewWillAppear() { }
func viewDidAppear() { }
func getDataFrom(url: URL) {
client.downloadFrom(url: url)
}
}
class ParentViewController: UIViewController {
func viewDidLoad() { }
func viewWillAppear() { }
func viewDidAppear() { }
func present() {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "ChildViewController") as! ChildViewController
vc.client = HTTPClient()
}
}
Here
HTTPClient
is being injected as property depedency tovar client
.ParentViewController
inject dependency toChildViewController
through property injection.
Hope it helps! If you liked the article and want to read more amazing articles, then do follow me here and show your support as it motivates me to write more.
Please share your feedback to improve the content and about the next topic you want to explore.
Thank you all for reading, happy coding!!
References
We can extended our functionality with Constructor injection, from calculating area of one shape to calculate area of n shape in less time.