I'm trying to implement Combine framework with Alamofire. But I have problems with generics, can you help me improving my code. So, my APIRouter class:
import Alamofire
import Foundation
public protocol APIConfiguration: URLRequestConvertible {
var method: HTTPMethod { get }
var baseURL: String { get }
var path: String { get }
var parameters: Parameters? { get }
func asURLRequest() throws -> URLRequest
}
public enum APIRouter: APIConfiguration {
case getPopularRequests
case getRegionAndCity
case getCities
case getComparableCities
case getSuggests(_ parameters: [String: String])
case getDrugs(_ parameters: [String: String])
// MARK: - HTTPMethod
public var method: HTTPMethod {
switch self {
case .getPopularRequests:
return .get
case .getRegionAndCity:
return .get
case .getCities:
return .get
case .getComparableCities:
return .get
case .getSuggests:
return .get
case .getDrugs:
return .get
}
}
// MARK: - BaseURL
public var baseURL: String {
return "https://example.com/api"
}
// MARK: - Path
public var path: String {
switch self {
case .getPopularRequests:
return "/goods/search/popular"
case .getRegionAndCity:
return "/handbooks/cities?q=&intersect_operation=&need_data=true&need_count=true&take=1000&skip=0&sort_by=name&sort_direction=asc"
case .getCities:
return "/handbooks/cities/"
case .getComparableCities:
return "/handbooks/cities?q=&intersect_operation=&need_data=true&need_count=true&take=1000&skip=0&sort_by=name&sort_direction=asc"
case .getSuggests:
return "/goods/search/suggests"
case .getDrugs:
return "/goods/search/global"
}
}
// MARK: - Parameters
public var parameters: Parameters? {
switch self {
case .getPopularRequests:
return nil
case .getRegionAndCity:
return nil
case .getCities:
return nil
case .getComparableCities:
return nil
case .getSuggests(let parameters):
return parameters
case .getDrugs(let parameters):
return parameters
}
}
// MARK: - URLRequestConvertible
public func asURLRequest() throws -> URLRequest {
let urlWithPathValue = baseURL path
var url = try urlWithPathValue.asURL()
var urlRequest = URLRequest(url: url)
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.httpMethod = method.rawValue
if let parameters = parameters {
switch self {
case .getPopularRequests, .getRegionAndCity, .getCities, .getComparableCities:
return urlRequest
case .getSuggests, .getDrugs:
var urlComponents = URLComponents(string: urlWithPathValue)!
urlComponents.queryItems = []
_ = parameters.map { (key, value) in
let item = URLQueryItem(name: key, value: value as? String)
urlComponents.queryItems?.append(item)
}
url = urlComponents.url!
urlRequest.url = url
}
}
return urlRequest
}
}
Than I have API Client class:
public protocol APICitiesScreenClientProtocol: AnyObject {
func getCities(completion: @escaping (Result<CitiesScreenModel, AFError>) -> Void)
func getCitiesWithCombine() -> AnyPublisher<Result<CitiesScreenModel, AFError>, Never>
}
public final class APIClient {
@discardableResult
private func performRequest<T: Decodable>(route: APIRouter, decoder: JSONDecoder = JSONDecoder(), completion: @escaping (Result<T, AFError>) -> Void) -> DataRequest {
return AF.request(route).responseDecodable(of: T.self, decoder: decoder) { response in
completion(response.result)
}
}
private func performCombineRequest<T: Decodable>(route: APIRouter, decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<Result<T, AFError>, Never> {
return AF.request(route).publishDecodable(type: T.self, decoder: decoder).result()
}
}
// MARK: - APICitiesScreenClientProtocol
extension APIClient: APICitiesScreenClientProtocol {
public func getCities(completion: @escaping (Result<CitiesScreenModel, AFError>) -> Void) {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
performRequest(route: .getCities, decoder: jsonDecoder, completion: completion)
}
public func getCitiesWithCombine() -> AnyPublisher<Result<CitiesScreenModel, AFError>, Never> {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
performCombineRequest(route: .getCities, decoder: jsonDecoder)
// return AF.request(APIRouter.getCities).publishDecodable(type: CitiesScreenModel.self, decoder: jsonDecoder).result()
}
}
And I am planning it to use like this:
APIClient().getCitiesWithCombine()
.receive(on: DispatchQueue.main)
.sink { [weak self] result in
switch result {
case .success(let data):
self?.prepareTableViewModel(for: data.data.elements)
self?.requestError = nil
case .failure(let error):
self?.requestError = error
}
}
.store(in: &cancellables)
When I use this line of code
return AF.request(APIRouter.getCities).publishDecodable(type: CitiesScreenModel.self, decoder: jsonDecoder).result()
, but if I try
performCombineRequest(route: .getCities, decoder: jsonDecoder)
I got "Generic parameter 'T' could not be inferred". Thank you for help.
CodePudding user response:
Yes, you need to provide the compiler the type you want as a result. You can either do this by adding a parameter to take the type, like Alamofire does (type: T.self
) or you can capture the publisher and provide the type.
let publisher: AnyPublisher<SomeType, Error> = performCombineRequest(...)
I suggest passing the type parameter.