I'm an iOS engineer at iXIT, @ branch10480. This time, I would like to write about "** DI (Dependency Injection) ** Possible implementation" supported by the product Sub that I am in charge of.
This DI (Dependency Injection) is described in Wikipedia as follows.
When creating a program using DI, the relationship between components is described using an interface, and no specific component is specified. By using another component, an external file, etc., which component is specifically used, the dependency between the components can be thinned.
At first, "I don't describe specific components" and "I can thin the dependencies between components" didn't come to my mind, so I couldn't imagine what kind of case it would be useful for.
So I will write it with the intention of explaining it to myself at that time.
From the conclusion,
Is an advantage.
This time, let's take an example of the function to acquire passport data and display a message according to the number of passport data. The image looks like this.
As a tentative specification
Let's go with this.
I wrote an example of implementation when I am not aware of DI. First, let's look at the Passport object and API client definition.
///Passport class
class Passport {
//Convert from JSON to Passport object array and return
static func parse(from json: JSON) -> [Passport] {
// ...
}
}
///API client
class APIClient {
static let apiServerBaseURL = "https://dev-server"
///Passport acquisition API
static func getPassports(completion: @escaping ([Passport])->Void) {
let url = URL(string: apiServerBaseURL + "/passport")
//Data acquisition to development server
// ...
//Receive result JSON from server->Generate an array of Passport objects from JSON
//For convenience, let the response JSON from the server be JSON
let passports = Passport.parse(from: JSON)
completion(passports)
}
}
The processing on the View side of the person who calls next is as follows.
///View side
class ViewController: UIViewController {
///When the screen is loaded
override func viewDidLoad() {
super.viewDidLoad()
APIClient.getPassports(completion: { passports in
if passports.isEmpty {
//"No data" alert display processing
}
})
}
}
By now, the specifications should have been met.
However, this always calls the API server (https: // dev-server), so you need to have a test API server that works. It's difficult to develop if the API is still under development.
This situation is ** dependent on an external API server **.
From here, we will rearrange this dependent part so that it can be replaced with other dependent parts (** dependency injection: DI **).
Swift allows you to abstract modules using protocol
.
This time, I will abstract the data acquisition part.
As for the function of the data acquisition part, if the function of "acquiring passport information" can be provided, it seems that the requirements of this sample can be satisfied. Therefore, the protocol is decided in this way.
///Passport data provision protocol
protocol PassportProviderProtocol {
func getPassports(completion: @escaping ([Passport])->Void)
}
** A light supplement to
protocol
** protocol is a list of properties and methods that you can access when you put an object in a variable of the defined protocol type and try to use it from the outside.Conversely, you cannot access anything other than what is written here from the outside, and you do not know what is going on inside. (In the example shown this time, it is not known from the outside whether the object included as PassportProviderProtocol is actually communicating with the server or just creating the object internally, but the getPassports () method Only the things that can be used are clear.)
Create an API client and mock to comply with this protocol.
The APIClient class is described as : PassportProviderProtocol
and adds a declaration that it complies with this protocol.
///API client
class APIClient: PassportProviderProtocol {
static let apiServerBaseURL = "https://dev-server"
///Passport acquisition API
static func getPassports(completion: @escaping ([Passport])->Void) {
let url = URL(string: apiServerBaseURL + "/passport")
//Data acquisition to development server
// ...
//Receive result JSON from server->Generate an array of Passport objects from JSON
//For convenience, let the response JSON from the server be JSON
let passports = Passport.parse(from: JSON)
completion(passports)
}
}
Let's make another mock for testing. This is also compliant with the PassportProviderProtocol.
///API client mock
class APIClientMock: PassportProviderProtocol {
enum TestPattern {
case empty
case notEmpty
}
var testPattern: TestPattern = .empty
///Get a passport
func getPassports(completion: @escaping ([Passport])->Void) {
//Make test results here
let passports: [Passport]
switch testPattern {
case .empty:
passports = []
case .notEmpty:
passports = [Passport()]
}
completion(passports)
}
}
Now you're ready to replace it! The implementation on the View side is like this.
///View side
class ViewController: UIViewController {
var passportProvider: PassportProviderProtocol = APIClient()
///When the screen is loaded
override func viewDidLoad() {
super.viewDidLoad()
passportsProvider.getPassports(completion: { passports in
if passports.isEmpty {
//"No data" alert display processing
}
})
}
}
You can now replace the production APIClient
object with the development / test APIClientMock
object by doing a property of type PassportProviderProtocol``passportsProvider
!
The usage image is like this.
let vc = ViewController()
vc.passportProvider = APIClientMock() //When you want to replace with a test module
self.navigationController?.pushViewController(vc, animated: true)
This time I tried to explain DI. In fact, if you develop with this configuration, you can proceed with API development at the same time without being affected by the progress of server-side development.
In addition, since it becomes necessary to separate modules as protocols, I felt that it would be easier to be aware of the range of functions that depend on them. I haven't started implementing the test code yet, so I'll try to summarize it if I can put it into shape.
It's been a long time, but thank you for reading! Next time is @oswhk! Thank you.