Dependency Injection

Amrit Pandey
3 min readDec 29, 2022

--

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:

  1. Code Reusability
  2. Ease of refactoring
  3. 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 inside DataDownload 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 with HTTPClient. In future our client can change n number of times but modifying our DataDownload 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 decouple DataDownload from concrete implementation of RemoteClient. Here Client1 wants to download data from FTP url, it can initaite FTPClient and inject it as dependency to DataDownload class while Client2 wants to download data from HTTP url, it can initaite HTTPClient and inject it to DataDownload 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

  1. Ease in testing.
  2. Quicker development of new features.
  3. Improved Xcode compile time(I will write a separate blog on this).
  4. Saves time and money.

Types of DI

  1. Constructor injection (explained above)
  2. Function injection
  3. 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 of RemoteClient . 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 to var client. ParentViewController inject dependency to ChildViewController 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

  1. https://essentialdeveloper.com
  2. https://developer.android.com/training/dependency-injection

We can extended our functionality with Constructor injection, from calculating area of one shape to calculate area of n shape in less time.

--

--

Amrit Pandey
Amrit Pandey

Written by Amrit Pandey

Mobile Lead: iOS | Flutter | Android | KMP

No responses yet