用 Swift 编写的 Moya 网络抽象层的微型版本。
仅支持通过SwiftPM安装。
⚠️如果您需要支持框架
Combine
不可用的平台(< iOS/tvOS 13,< macOS 10.15),请改用分支support/without-combine
。
[hidecontent type="logged" desc="隐藏内容:登录后可查看"]
使用指定为参数的请求参数/数据创建enum
具有所有支持端点的Api 。cases
例如,在为Microsoft Translator API编写客户端时:
enum MicrosoftTranslatorApi {
case languages
case translate(texts: [String], from: Language, to: [Language])
}
Endpoint
兼容enum
为您的 Api 添加使其兼容的扩展Endpoint
,这意味着您需要为以下协议添加实现:
public protocol Endpoint {
associatedtype ClientErrorType: Decodable
var decoder: JSONDecoder { get }
var encoder: JSONEncoder { get }
var baseUrl: URL { get }
var headers: [String: String] { get }
var subpath: String { get }
var method: HttpMethod { get }
var queryParameters: [String: QueryParameterValue] { get }
var mockedResponse: MockedResponse? { get }
}
使用switch
语句 overself
来区分不同的情况(如果需要)并提供协议要求的适当数据(使用Value Bindings)。
Decodable
通过使用类型中预先实现的方法之一调用提供预期结果类型(如果有)的 API 端点ApiProvider
:
/// Performs the asynchornous request for the chosen endpoint and calls the completion closure with the result.
performRequest<ResultType: Decodable>(
on endpoint: EndpointType,
decodeBodyTo: ResultType.Type,
completion: @escaping (Result<ResultType, ApiError<ClientErrorType>>) -> Void
)
/// Performs the request for the chosen endpoint synchronously (waits for the result) and returns the result.
public func performRequestAndWait<ResultType: Decodable>(
on endpoint: EndpointType,
decodeBodyTo bodyType: ResultType.Type
)
对于不需要响应主体的端点,还有额外的方法:
/// Performs the asynchronous request for the chosen write-only endpoint and calls the completion closure with the result.
performRequest(on endpoint: EndpointType, completion: @escaping (Result<EmptyBodyResponse, ApiError<ClientErrorType>>) -> Void)
/// Performs the request for the chosen write-only endpoint synchronously (waits for the result).
performRequestAndWait(on endpoint: EndpointType) -> Result<EmptyBodyResponse, ApiError<ClientErrorType>>
这里返回EmptyBodyResponse
的只是一个空类型,所以你可以忽略它。
以下是您可以使用 Mircoya 拨打电话的完整示例:
let provider = ApiProvider<MicrosoftTranslatorEndpoint>()
let endpoint = MicrosoftTranslatorEndpoint.translate(texts: ["Test"], from: .english, to: [.german, .japanese, .turkish])
provider.performRequest(on: endpoint, decodeBodyTo: [String: String].self) { result in
switch result {
case let .success(translationsByLanguage):
// use the already decoded `[String: String]` result
case let .failure(apiError):
// error handling
}
}
// OR, if you prefer a synchronous call, use the `AndWait` variant
switch provider.performRequestAndWait(on: endpoint, decodeBodyTo: [String: String].self) {
case let .success(translationsByLanguage):
// use the already decoded `[String: String]` result
case let .failure(apiError):
// error handling
}
get()
请注意,您还可以使用Swift 5 类型的throwing函数Result
而不是使用 a switch
:
provider.performRequest(on: endpoint, decodeBodyTo: [String: String].self) { result in
let translationsByLanguage = try result.get()
// use the already decoded `[String: String]` result
}
// OR, if you prefer a synchronous call, use the `AndWait` variant
let translationsByLanguage = try provider.performRequestAndWait(on: endpoint, decodeBodyTo: [String: String].self).get()
// use the already decoded `[String: String]` result
甚至在类型上定义了有用的函数方法,Result
例如map()
,flatMap()
或mapError()
和flatMapError()
。有关详细信息,请参阅本文中的“转换结果”部分。
performRequest(on:decodeBodyTo:)
如果您在项目中使用 Combine(例如,因为您使用的是 SwiftUI),您可能希望将对or 的调用替换performRequest(on:)
为 Combine 调用publisher(on:decodeBodyTo:)
or publisher(on:)
。这将为您提供AnyPublisher
要订阅的请求流。在成功的情况下,您将收到解码后的类型化对象,在错误的情况下,您将收到与完成闭包ApiError
中完全相同的对象performRequest
。Result
但是您可以使用Combine 框架中的sink
or来代替类型。catch
例如,与 Combine 的用法可能如下所示:
var cancellables: Set<AnyCancellable> = []
provider.publisher(on: endpoint, decodeBodyTo: TranslationsResponse.self)
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
.subscribe(on: DispatchQueue.global())
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { _ in }
receiveValue: { (translationsResponse: TranslationsResponse) in
// do something with the success response object
}
)
.catch { apiError in
switch apiError {
case let .clientError(statusCode, clientError):
// show an alert to customer with status code & data from clientError body
default:
logger.handleApiError(apiError)
}
}
.store(in: &cancellables)
如果您在项目中使用 Swift 5.5,并且您的最低目标是 iOS/tvOS 15+、macOS 12+ 或 watchOS 8+,您可能希望改用该async
方法response
。例如,用法可能如下所示:
let result = await provider.response(on: endpoint, decodeBodyTo: TranslationsResponse.self)
switch result {
case let .success(translationsByLanguage):
// use the already decoded `[String: String]` result
case let .failure(apiError):
// error handling
}
的初始化程序ApiProvider
接受一个对象数组Plugin
。您可以实现自己的插件或使用Plugins目录中的现有插件之一。以下是自定义Plugin
子类可以覆盖的回调:
/// Called to modify a request before sending.
modifyRequest(_ request: inout URLRequest, endpoint: EndpointType)
/// Called immediately before a request is sent.
willPerformRequest(_ request: URLRequest, endpoint: EndpointType)
/// Called after a response has been received & decoded, but before calling the completion handler.
didPerformRequest<ResultType: Decodable>(
urlSessionResult: (data: Data?, response: URLResponse?, error: Error?),
typedResult: Result<ResultType, ApiError<EndpointType.ClientErrorType>>,
endpoint: EndpointType
)
Endpoint
为其所需的大部分方法提供默认实现,即:
public var decoder: JSONDecoder { JSONDecoder() }
public var encoder: JSONEncoder { JSONEncoder() }
public var headers: [String: String] {
[
"Content-Type": "application/json",
"Accept": "application/json",
"Accept-Language": Locale.current.languageCode ?? "en"
]
}
public var queryParameters: [String: QueryParameterValue] { [:] }
public var mockedResponse: MockedResponse? { nil }
所以从技术上讲,该Endpoint
类型只需要您指定以下 4 件事:
protocol Endpoint {
associatedtype ClientErrorType: Decodable
var subpath: String { get }
var method: HttpMethod { get }
}
对于您要访问的简单 API,这可以节省时间(/代码)。您还可以使用EmptyBodyResponse
type forClientErrorType
来忽略客户端错误主体结构。
Microya 支持在您的测试中模拟响应。为此,只需ApiProvider
在您的测试中初始化一个不同的并指定一个给定的delay
和scheduler
作为mockingBehavior
参数。
现在,Microya 不会进行实际调用,而是使用mockedResponse
您的类型中提供的计算属性进行响应Endpoint
。
请注意,.delay
模拟行为是为与 Combine 调度程序一起使用而设计的。使用DispatchQueue.test
fromcombine-schedulers
库(包含在 Microya 中)来控制测试中的时间,因此在使用.delay
.
例如,您可能希望在测试中添加一个扩展以提供一个.mocked
属性,以便在您需要时使用ApiProvider
:
import CombineSchedulers
import Foundation
import Microya
let testScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.test
extension ApiProvider {
static var mocked: ApiProvider<MicrosoftTranslatorEndpoint> {
ApiProvider<MicrosoftTranslatorEndpoint>(
baseUrl: URL(string: "https://api.cognitive.microsofttranslator.com")!,
mockingBehavior: MockingBehavior(delay: .seconds(0.5), scheduler: testScheduler.eraseToAnyScheduler()
)
}
}
现在,在您的测试中,您只需调用testScheduler.advance(by: .milliseconds(300))
快进时间即可让您的测试保持快速。
[/hidecontent]