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(...)
我建议传递类型参数。
我正在尝试使用 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(...)
我建议传递类型参数。