Home > Enterprise >  Dependency Inversion with assosiatedType in protocol
Dependency Inversion with assosiatedType in protocol

Time:11-27

I am having a hard time trying to implement Dependency Inversion. Looking around, find a fantastic article Swift Type Erasure. I am not sure how to can I get advantage in my situation. Here is what I am trying to achieve.

Protocol for Networking

protocol Networkable {
    associatedtype Response: Decodable

    func request(handler: @escaping((Result<Response, Error>) -> ()))
}

Concrete Implementation of Networkable protocol

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 that is dependent on Networkable protocol

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")
        }
    }
}

Usage:

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 {}

Questions:

  1. Is this a correct way to implement dependency-inversion?
  2. How can I achieve Generic behavior on Network class and still open for testing?

CodePudding user response:

Your ViewModel class is ill-formed:

class ViewModel<T: Decodable> {
    let networker: Networker<T>
    
}

Otherwise you might create a type-erased AnyNetworkable concrete type, which you could use as in:

class ViewModel<T: Decodable> {
    let networker: AnyNetworkable<T>
    
}

Or you could go with an even more generic approach as in:

class ViewModel<R, T: Networkable> where T.Response == R {
     let networker: T
}

Obviously the same reflects on your View type, which you also need to make either generic or specialised over a specific concrete type of ViewModel.

  • Related