Declan McKenna
Declan McKenna

Reputation: 4870

How to reference the property of a protocol that is an associatedType?

I have a NetworkProvider class that handles my network requests and is instantiated with an associatedtype of a TargetType protocol that defines what's needed for an endpoint.

Right now I utilize NetworkProvider as follows:

let provider = NetworkProvider<IMDBAPI>()
provider.request<[Movie]>(IMDBAPI.getTop250Movies){
    //When successful returns HTTPResponse.success([Movie])
}

Right now the request<T: Decodable> must reference a Decodable model. However I'd like to be able to have this inferred from the IMDBAPI.getTop250Movies as this will always return an array of Movie objects so it seems counterproductive to require a developer to have advance knowledge of what the return type of an endpoint is prior to utilising it.

Is it possible to infer the model returned by an an Endpoint/TargetType through the associatedtype protocol passed to it?


Code

TargetType

public protocol TargetType {

    var baseURL: URL { get }

    var path: String { get }

    var method: HTTPMethod { get }

    var headers: [String: String]? { get }

    var task: HTTPRequestTask { get }

    //var returnType: Decodable? { get } //This is what I'd like to use.
}

NetworkProvider

public protocol NetworkProviderType {
    associatedtype Target: TargetType
}

public class NetworkProvider<Target: TargetType>: KindredProviderType {

    public func request<T: Decodable>(_ target: Target, handler: @escaping (HTTPResponse<T>) -> ()) {

        let endpoint = NetworkProvider.defaultEndpointMapping(for: target)

        guard let urlRequest = try? endpoint.urlRequest() else {
            return handler(HTTPResponse.error(HTTPError.failedToCreate(url: endpoint.url)))
        }

        session.runDataTask(with: urlRequest, completionHandler: { (data, response, error) in
            let result = HTTPResponse<T>.createFrom(url: urlRequest.url!, error: error, data: data, response: response)

            if case let HTTPResponse.error(error) = result {
                loggingPrint(error)
            }
            handler(result)
        })
    }
}

Example TargetType Implementation

enum SplunkService {
    case recordLogin(report: SplunkJourneyReporting)
    case recordGameLaunch(report: SplunkJourneyReporting)
    case recordError(report: SplunkEventReport)
}

extension SplunkService: TargetType {
    var task: HTTPRequestTask {
        switch self {
        case .recordError(let report):
            return HTTPRequestTask.requestBodyParameters(report.toDictionary())
        case .recordGameLaunch(let report), .recordLogin(let report):
            return HTTPRequestTask.requestBodyParameters(report.toDictionary())
        }
    }

    var baseURL: URL { return URL(string: "https://api.unibet.com")! }

    var path: String {
        switch self {
        case .recordLogin(_):
            return "/eum-collector/journeys/login/reports"
        case .recordGameLaunch(_):
            return "/eum-collector/journeys/game_launch/reports"
        case .recordError(_):
            return "/eum-collector/events"
        }
    }

    var method: HTTPMethod {
        return .post
    }

    var headers: [String: String]? {
        return [HTTPHeaderField.contentType.rawValue: "application/json"]
    }
}

Upvotes: 0

Views: 64

Answers (1)

Vader
Vader

Reputation: 77

I hope this helps. Please review the following comment inline with the code I attached...

// the compiler understand exactly what type `T` is because we `init` the class using `T` and that is also the same type used to conform to `TargetType`, in other words if I do `NetworkProvider(returnType: decodableThing)`, the compiler knows that `T` = (the class of `decodableThing`)
import UIKit
import Foundation

public protocol TargetType {

    associatedtype ReturnTypeUsed: Decodable

    var returnType: ReturnTypeUsed? { get } //This is what I'd like to use.
}

public class NetworkProvider<T> {

    // The compiler understand exactly what type `T` is because we `init` the class using `T` and that is also the same type used to conform to `TargetType`
    // in other words if I do `NetworkProvider(returnType: decodableThing)`, the compiler knows that `T` = (the class of `decodableThing`)
    public typealias ReturnTypeUsed = T

    public var returnType: T?

    init(returnType: T) {
        self.returnType = returnType
    }

}

class Movie: Decodable {

}

let someMovie = Movie()

class IMDBAPI: TargetType {

    var returnType: Movie?

}

let networkProvider = NetworkProvider(returnType: someMovie)

print(networkProvider.returnType) // returns `Optional(__lldb_expr_5.Movie)

Upvotes: 1

Related Questions