Alamofire 与 Swift 合并

Alamofire with Swift Combine

我正在尝试使用 Alamofire 实现 Combine 框架。但是我对泛型有问题,你能帮我改进我的代码吗? 所以,我的 API 路由器 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
    }
}

比我有 API 客户 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()
    }
}

我打算这样使用它:

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)

当我使用这行代码时

return AF.request(APIRouter.getCities).publishDecodable(type: CitiesScreenModel.self, decoder: jsonDecoder).result()

,但如果我尝试

performCombineRequest(route: .getCities, decoder: jsonDecoder)

我收到“无法推断通用参数 'T'”。 谢谢你的帮助。

是的,您需要为编译器提供所需的结果类型。你可以通过添加一个参数来获取类型,就像 Alamofire 那样(type: T.self,你可以默认使用 T.Type = T.self)或者你可以捕获发布者并提供类型。

let publisher: AnyPublisher<SomeType, Error> = performCombineRequest(...)

我建议传递类型参数。