Home > Net >  Alamofire with Swift Combine
Alamofire with Swift Combine

Time:03-02

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.

  • Related