I'm currently building a small web stack in swift for use in my iOS apps. It consists of multiple components and I want to make it resource specific;
To that end, I have a WebService class composed of two parts; a part that handles the request logic, and a part that handles the parsing.
The catch here is that the parsing is defined by a ParserType protocol with an associated type ModelType, which needs to be specified by the adopter. It parses Any? to that ModelType:
public protocol ParserType {
associatedtype ModelType
func parse(_ data: Any?) throws -> ModelType
}
This is no problem on its own; a concrete parser could look like this (just forgive me the force casting here):
public class SimpleJSONParser<T: Codable>: ParserType {
public typealias ModelType = T
private let jsonDecoder: JSONDecoder
public init(jsonDecoder: JSONDecoder = JSONDecoder()) {
self.jsonDecoder = jsonDecoder
}
open func parse(_ data: Any?) throws -> T {
let response = try jsonDecoder.decode(T.self, from: data as! Data)
return response
}
}
Now we have a concrete JSON parser that tries to parse Any? to T. That's cool, so we want to inject it into WebService, so it can be used. It looks like this:
public class WebService<ModelType, Parser: ParserType> {
public typealias ModelType = Parser.ModelType
public typealias WebServiceCompletion = (Result<ModelType>) -> Void
private let endpoint: String
private let parser: Parser
private let transport: ServiceTransport
public init(transport: ServiceTransport, endpoint: String, parser: Parser) {
self.transport = transport
self.endpoint = endpoint
self.parser = parser
}
@discardableResult
open func fetch(path: String? = nil, headers: [String: String]? = nil, parameters: [String: String]? = nil, autoResumeTask: Bool = true, completion: @escaping WebServiceCompletion) -> ServiceTransportTask {
let endpointToUse = endpoint + (path ?? "")
let task = transport.perform(action: .get, path: endpointToUse, parameters: parameters, headers: headers, body: nil)
.completion { response in
switch response {
case .failure(let error):
completion(.failure(error))
case .success(let data):
do {
let result: ModelType = try self.parser.parse(data)
completion(.success(result))
} catch let error {
completion(.failure(error))
}
}
}
if autoResumeTask {
task.resume()
}
return task
}
}
The issue I have is that when I want to use this, I have to specify to the WebService that I want to use it for a type CategoryArray for example, with a parser that parses JSON to CategoryArray, i.e. JSONParser<CategoryArray>, and then also inject such a parser into the WebService:
let transport = HTTPTransport(baseURL: URL(string: baseURLString)!)
let parser = JSONParser<CategoryArray>()
let webService: WebService<CategoryArray, JSONParser<CategoryArray>> webService = WebService(transport: transport, endpoint: endPoint, parser: parser)
It feels a bit weird to me in the sense that I have to define the model type to use in both WebService and Parser, and also define the parser type.
Is my approach too generic, or can I factor out something here?