#swift #xcode #dependency-injection #dependency-inversion
Вопрос:
Мне трудно реализовать инверсию зависимостей. Оглядевшись вокруг, найдите фантастическую статью об удалении типа Swift. Я не уверен, как я могу получить преимущество в своей ситуации. Вот чего я пытаюсь достичь.
Протокол для создания сетей
protocol Networkable {
associatedtype Response: Decodable
func request(handler: @escaping((Result<Response, Error>) -> ()))
}
Конкретное осуществление Networkable
протокола
final class Networker<Response: Decodable>: Networkable {
private let session: URLSession
private let url: URL
init(session: URLSession, url: URL) {
self.session = session
self.url = url
}
func request(handler: @escaping ((Result<Response, Error>) -> ())) {
session.dataTask(with: url) { data, response, error in
handler(.success(try! JSONDecoder().decode(Response.self, from: data!)))
}
}
}
A ViewModel
, который зависит от Networkable
протокола
class ViewModel {
let networker: Networkable
// Error-> Protocol 'Networkable' can only be used as a generic constraint because it has Self or associated type requirements
init(netwrorker: Networkable) {
self.networker = netwrorker
}
func execute() {
networker.request { _ in
print("Responsed")
}
}
}
Использование:
class View {
let viewModel: ViewModel
init() {
let networker = Networker<ResponseModel>(session: .shared, url: URL(string: "SOME URL")!)
self.viewModel = ViewModel(netwrorker: networker)
}
func load() {
viewModel.execute()
}
}
struct ResponseModel: Decodable {}
Вопросы:
- Является ли это правильным способом реализации инверсии зависимостей?
- Как я могу добиться общего поведения в сетевом классе и при этом оставаться открытым для тестирования?
Ответ №1:
Ваш ViewModel
класс плохо сформирован:
class ViewModel<T: Decodable> {
let networker: Networker<T>
…
}
В противном случае вы можете создать типизированный AnyNetworkable
конкретный тип, который вы могли бы использовать, как в:
class ViewModel<T: Decodable> {
let networker: AnyNetworkable<T>
…
}
Или вы могли бы использовать еще более общий подход, как в:
class ViewModel<R, T: Networkable> where T.Response == R {
let networker: T
}
Очевидно, то же самое отражается на вашем View
типе, который вам также необходимо сделать либо общим, либо специализированным для определенного конкретного типа ViewModel
.