开源
Microya:用 Swift 编写的 Moya 网络抽象层的微型版本
来源:元经纪     阅读:535
网站管理员
发布于 2023-05-18 09:37
查看主页

概述

用 Swift 编写的 Moya 网络抽象层的微型版本。

主题

安装

仅支持通过SwiftPM安装。

⚠️如果您需要支持框架Combine不可用的平台(< iOS/tvOS 13,< macOS 10.15),请改用分支support/without-combine

用法

[hidecontent type="logged" desc="隐藏内容:登录后可查看"]

第 1 步:定义端点

使用指定为参数的请求参数/数据创建enum具有所有支持端点的Api 。cases

例如,在为Microsoft Translator API编写客户端时:

enum MicrosoftTranslatorApi {
    case languages
    case translate(texts: [String], from: Language, to: [Language])
}

第 2 步:使您的 ApiEndpoint兼容

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)。

切换我看一个例子

第 3 步:使用结果类型调用 API 端点

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中完全相同的对象performRequestResult但是您可以使用Combine 框架中的sinkor来代替类型。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,这可以节省时间(/代码)。您还可以使用EmptyBodyResponsetype forClientErrorType来忽略客户端错误主体结构。

测试

Microya 支持在您的测试中模拟响应。为此,只需ApiProvider在您的测试中初始化一个不同的并指定一个给定的delayscheduler作为mockingBehavior参数。

现在,Microya 不会进行实际调用,而是使用mockedResponse您的类型中提供的计算属性进行响应Endpoint

请注意,.delay模拟行为是为与 Combine 调度程序一起使用而设计的。使用DispatchQueue.testfromcombine-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]

 
免责声明:本文为用户发表,不代表网站立场,仅供参考,不构成引导等用途。 开源
27888元!大疆发布DJI Matrice 4E无人机:可照亮百米之外目标
钟睒睒:农夫山泉永远属于中国 我挣的每一分钱都是干净值得检验
强国有我展气象青年担当
《人工智能企业等级评定规范》正式发布!
爬山更轻松!黄山3款登山“黑科技”公开试用

首页

分类

定制方案

消息

我的