如何引用为associatedType的协议的属性?

时间:2019-05-08 09:23:09

标签: swift generics network-programming

我有一个NetworkProvider类,该类处理我的网络请求,并使用associatedtype协议的TargetType实例化,该协议定义了端点所需的内容。

现在我按如下方式利用NetworkProvider:

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

现在request<T: Decodable>必须引用可解码模型。但是,我希望能够从IMDBAPI.getTop250Movies推断出这一点,因为它将总是返回一个Movie对象的数组,因此要求开发人员事先了解返回类型是似乎适得其反端点的使用是在使用它之前。

是否可以通过传递给它的关联类型协议来推断Endpoint / TargetType返回的模型?


代码

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

TargetType实现示例

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

1 个答案:

答案 0 :(得分:1)

我希望这会有所帮助。请使用我所附的代码内联查看以下注释...

// 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)