Fetcher
介绍
Fetcher 是一个用于 Swift 的小型 HTTP 网络库。其主要目标是简化像发送 REST 请求这样的常见任务。网络是一个非常复杂的话题,我们的目标不是涵盖所有可以做的事情。但我们提供一组 API,允许您实现所需的内容或自定义 Fetcher 的行为(这很有用,如果您的服务器由于某种原因不遵守任何标准)。
主要特性
- 易于使用的 API
- 编译时安全性(尽可能多)
- 使用 DataMapper 抽象数据编码
- 几乎一切都可以自定义
- 后台线程上的数据映射
- 按照 DRY 原则设计
- 支持 RxSwift
变更日志
所有变更和新功能的列表可以在此处找到。
需求
- Swift 3
- iOS 8+
安装
CocoaPods
Fetcher 可以通过 CocoaPods 获取。要安装它,只需将以下行添加到您的 Podfile 中的测试目标
pod "Fetcher"
这会自动包含 "Core" 和 "AlamofireRequestPerformer" 子组件。
如果您只想使用 Fetcher 而不带 RequestPerformers(您有自己的实现),则使用
pod "Fetcher/Core"
Fetcher 支持 RxSwift
pod "Fetcher/RxFetcher"
注意:"RxFetcher" 子组件不依赖于 "AlamofireRequestPerformer"。因此,如果您想使用它,需要额外添加
pod "Fetcher"
或
pod "Fetcher/AlamofireRequestPerformer"
用法
以下是该库提供的所有功能和它们的使用方法。一些用法示例可以在 测试 中找到。本文档假设您已经熟悉 DataMapper。您还应了解一些 HTTP 的基础知识。
快速概览
以下是一个简单的用例示例。它不会解释所有使用的概念。请参阅相应章节以获取解释。
假设我们想要发送 GET
请求,该请求会从我们的服务器检索一些数据。我们的数据如下所示
struct ExampleObject {
let id: String?
let text: String?
}
为了模拟我们的服务器,我们将使用 httpbin.org/get。它返回您发送的所有内容。例如,查看 httpbin.org/get?id=1&text=a 的响应。
let fetcher = Fetcher(requestPerformer: AlamofireRequestPerformer())
fetcher.request(GET<Void, SupportedType>("https://httpbin.org/get", modifiers: URLQueryItem(name: "id", value: "1"), URLQueryItem(name: "text", value: "a")), callback: { response in
switch response.result {
case .success(let value):
let object = ExampleObject(id: value.dictionary?["args"]?.dictionary?["id"]?.string,
text: value.dictionary?["args"]?.dictionary?["text"]?.string)
print(object) // ExampleObject(id: Optional("1"), text: Optional("a"))
case .failure(let error):
// Handle error
break
}
})
这可能是您可以编写的最简单的代码(同时也是最丑陋的)。同时,它与 Alamofire 代码几乎没有区别。然而,我们可以稍作改进
let fetcher = Fetcher(requestPerformer: AlamofireRequestPerformer())
fetcher.request(Endpoints.get(id: "1", text: "a"), callback: { response in
switch response.result {
case .success(let value):
// value was already transformed by DataMapper.
print(value)
case .failure(let error):
// Handle error
break
}
})
struct Endpoints: EndpointProvider {
static func get(id: String, text: String) -> GET<Void, ExampleObject> {
return create("https://httpbin.org/get", modifiers: URLQueryItem(name: "id", value: "\(id)"), URLQueryItem(name: "text", value: "\(text)"))
}
}
struct ExampleObject: Deserializable {
let id: String?
let text: String?
init(_ data: DeserializableData) throws {
id = data["args"]["id"].get()
text = data["args"]["text"].get()
}
}
在这里使用了DataMapper来去除请求映射,并将端点移动到额外的结构体中,以便于重用。然而,当我们的代码进一步扩展时,仍存在一些问题,这些问题可能会变得更糟。例如,如果我们想添加新的数据对象,那么就必须在每一个地方都写入data["args"]
,因为服务端API的要求。此外,如果我们添加新的端点,那么基本URL将会出现在多个地方。
let fetcher = Fetcher(requestPerformer: AlamofireRequestPerformer())
fetcher.register(requestEnhancers: HttpBinResponseTranslation())
fetcher.register(requestModifiers: BaseUrl(baseUrl: "https://httpbin.org"))
fetcher.request(Endpoints.post(), input: ExampleObject(id: "1", text: "a")) {
if let error = $0.result.error {
// Handle error
}
}
struct Endpoints: EndpointProvider {
static func get(id: String, text: String) -> GET<Void, ExampleObject> {
return create("get", modifiers: URLQueryItem(name: "id", value: "\(id)"), URLQueryItem(name: "text", value: "\(text)"))
}
static func post() -> POST<ExampleObject, Void> {
return create("post")
}
static func put() -> PUT<ExampleObject, Void> {
return create("put")
}
static func delete(id: String) -> DELETE<Void, Void> {
return create("delete", modifiers: URLQueryItem(name: "id", value: "\(id)"))
}
}
struct HttpBinResponseTranslation: RequestEnhancer {
func deenhance(response: inout Response<SupportedType>) {
response = response.map { $0.dictionary?["args"] ?? .null }
}
}
struct ExampleObject: Mappable {
var id: String?
var text: String?
init(id: String?, text: String?) {
self.id = id
self.text = text
}
init(_ data: DeserializableData) throws {
try mapping(data)
}
mutating func mapping(_ data: inout MappableData) throws {
data["id"].map(&id)
data["text"].map(&text)
}
}
使用HttpBinResponseTranslation
解决了data["args"]
的问题,多亏了BaseUrl
,我们不再需要在每个地方都编写基本URL(若想了解这一切是如何工作的,请参阅请求增强器)。此例子的另一个重要功能是fetcher.request(Endpoints.post(), input: ExampleObject(id: "1", text: "a"))
,它展示了如何将一些数据发送到服务器。
获取器
Fetcher
负责处理请求和响应。它使用RequestPerformer
(见请求执行者)实际完成数据传输。
在init
中可以传递更多参数
- objectMapperPolymorph:
Polymorph
- 用于映射传递给Fetcher
的对象的Polymorph(见DataMapper) - errorHandler:
ErrorHandler
- 见错误处理器 - callQueue:
DispatchQueue
- 几乎所有Fetcher逻辑都完成的队列(包括对象映射)。默认是后台队列。 - callbackQueue:
DispatchQueue
- 调用请求中传入的回调的队列。默认是主队列。
Fetcher
可以通过register
方法进行额外的配置
func register(requestEnhancers: [RequestEnhancer])
func register(requestEnhancers: RequestEnhancer...)
func register(requestModifiers: [RequestModifier])
func register(requestModifiers: RequestModifier...)
这用于添加在每个请求中使用的RequestEnhacer
和RequestModifier
(见请求增强器)。
根据其本质,Fetcher
通常只会在代码中的一个地方被创建。但有时你可能想要更多(例如,如果你想要两个具有不同基本URL的服务器)。在这种情况下,将一个Fetcher
实例的设置复制到另一个实例可能很有用。这可以通过以下方式实现
init(copy fetcher: Fetcher)
请求
request
支持所有类型的数据。它们之间主要的区别在于是否存在输入数据以及是否使用DataMapper。以下是request
重载的示例
func request<IN: Serializable, OUT: Deserializable>(_ endpoint: Endpoint<IN, OUT>, input: IN, callback: @escaping (Response<OUT>) -> Void) -> Cancellable
《Endpoint (参见Endpoint) 的第一种泛型类型表示要发送到服务器的数据类型,第二种类型表示服务器将发送回什么数据。 Void
表示没有数据, NSData
表示不使用DataMapper, SupportedType
允许您手动执行转换。如果 Endpoint
的输入类型为 Void
,则请求没有 input
参数。
request
支持了DataMapper 所有的功能(包括上述提到的异常)。尽管可以创建任意输入和输出类型的 Endpoint
,但不能在 request
中使用它们。
从 request
返回的 Cancellable
可以用来取消请求,如果请求不再必要时。
let cancellable = fetcher.request(Endpoints.endpoint()) { response in
// Do some stuff
}
cancellable.cancel()
重试
extension Request {
func retry(max: Int = Int.max, delay: DispatchTimeInterval = .seconds(0), failCallback: () -> Void = {})
}
有时重复执行请求可能会导致完全不同的行为。例如,请求失败因为没有网络连接。在这种情况下,您可能稍后再尝试请求。正是为了这种情况, Request
(见 Request) 提供了 retry
方法。
与手动调用请求相同。 max
表示请求将重试多少次。如果达到这个计数,则调用 failCallback
而不是重复请求。每次调用 retry
只重试请求一次。但由于这通常是在回调中完成的(每次尝试都会调用),所以 retry
会循环调用(这里就是 max
作用的场景)。 delay
修改了 Fetcher
在尝试之前应该等待多长时间。从原始请求获得的 Cancallable
对重试请求也有效。示例
fetcher.request(...) { response in
guard let value = response.result.value else {
return response.request.retry(max: 3) {
print("error")
}
}
print(value)
})
在这里,只有在 result
是失败的情况下才会调用 retry
。调用 retry
后,此回调结束。下一次调用时会使用不同的 Response
,这个过程会重复(在这种情况下最多三次)。如果在所有这些重试之后,result
仍然是失败,则调用 print("error")
,不再进行更多尝试。
请求
struct Request {
var modifiers: [RequestModifier] = []
var URLRequest: URLRequest
var callback: (Response<Data>) -> Void
var cancellable: Cancellable
var retried = 0
var retryClosure: (Request, Int, DispatchTimeInterval, () -> Void) -> Void
func retry(max: Int = Int.max, delay: DispatchTimeInterval = .seconds(0), failCallback: () -> Void = {})
}
Request
是 NSURLRequest
的包装器。它提供了来自 NSURLRequest
的所有方法的代理。您可以在 RequestEnhancer
(参见RequestEnhancer) 和 Response
中访问 Request
。您可以在 RequestEnhancer
中随意修改 Request
。 cancellable
与 Fetcher.request
返回的相同的 Cancellable
相同,这是创建这个 Request
的请求。 retried
计算调用了多少次 retry
, retryClosure
代表 retry
的实现。
响应
struct Response<T> {
public let result: FetcherResult<T>
public let rawResponse: HTTPURLResponse?
public let rawData: Data?
public let request: Request
}
响应
表示请求
的结果(服务器对它的响应)。在fetcher.request
的每个回调中,您都会得到一个响应
实例。声明
T
与Endpoint
中的OUT
具有相同的类型。FetcherResult<T>
是Result<T, FetcherError>
的类型别名。
还有一些扩展
extension Response {
func map<U>(_ transform: (T) -> U) -> Response<U>
func flatMap<U>(_ transform: (T) -> FetcherResult<U>) -> Response<U>
var rawString: String?
}
map
和flatMap
与Result
中的方式相同(并且仅应用于result
)。rawString
返回使用响应头中指定的字符编码解码的rawData
。
端点
Endpoint
是一个类,它定义了请求的URL(path
)、使用的InputEncoding
、特定于请求的RequestModifier
以及请求的HTTP方法。其实现有以下构造函数。
init(_ path: String, modifiers: [RequestModifier])
init(_ path: String, inputEncoding: InputEncoding, modifiers: [RequestModifier])
init(_ path: String, modifiers: RequestModifier...)
init(_ path: String, inputEncoding: InputEncoding, modifiers: RequestModifier...)
通常您不会直接使用Endpoint
或创建其子类。请参阅方法。
端点提供者
EndpointProvider
是一个协议,支持创建可重用端点的模式。它提供了一个签名与端点相同的静态方法create
,但根据上下文创建特定的实现。该模式如下所示
struct Endpoints: EndpointProvider {
static func get(id: String, text: String) -> GET<Void, ExampleObject> {
return create("get", modifiers: URLQueryItem(name: "id", value: "\(id)"), URLQueryItem(name: "text", value: "\(text)"))
}
static func post() -> POST<ExampleObject, Void> {
return create("post")
}
}
请注意,create
可以替换为GET
和POST
,但我们认为只在一个地方指定类型更好。另一个优点是,您可以为每个Endpoint
添加隐式RequestModifier
(请参阅RequestModifier)。为此,请实现implicitModifiers
struct Endpoints: EndpointProvider {
static var implicitModifiers: [RequestModifier] = [StatusCodeResponseVerifier(code: 200)]
static func post() -> POST<ExampleObject, Void> {
return create("post")
}
}
还可以将端点声明为属性
struct Endpoints: EndpointProvider {
static var post = POST<ExampleObject, Void>("post")
}
这仅在没有直接传递任何参数到URL时可以进行。在本例中,不会工作。如果需要,可以像这样修复
struct Endpoints: EndpointProvider {
static var post: POST<ExampleObject, Void> = create("post")
}
方法
这些都是预定义的Endpoint
实现,代表了某些HTTP方法
- 连接
- 删除
- 获取
- 标题
- 选项
- 修补程序
- POST
- PUT
- TRACE
请求增强器
protocol RequestEnhancer {
static var priority: RequestEnhancerPriority { get }
var instancePriority: RequestEnhancerPriority? { get }
func enhance(request: inout Request)
func deenhance(response: inout Response<SupportedType>)
}
请求增强器
是一种修改Fetcher
行为协议。
增强
在输入数据被编码到其中(请参见输入编码)之后并在RequestPerformer
(请参见请求执行器)执行之前应用于每个Request
。您可以根据自己的意愿修改Request
。默认实现不做任何事情。
去增强
与Request
相同,但用于传入的Response
。它在输出数据解码(请参见输入编码)后和请求回调之前被调用。默认实现不做任何事情。
优先级
用于决定多个RequestEnhancer
实例应该按照什么顺序应用。默认值是.normal
。
instancePriority
解决了每个RequestEnhancer
实例可能有不同优先级的问题(priority
是静态的)。默认值是空值,在这种情况下使用priority
。如果instancePriority
不为空值,则使用它替代priority
。
可以通过register
方法(请参见Fetcher)将RequestEnhancer
添加到Fetcher
中。
请求增强器优先级
enum RequestEnhancerPriority {
case low
case normal
case high
case fetcher
case max
case custom(value: Int)
}
RequestEnhancerPriority
表示RequestEnhancer
的优先级。顺序是从最低到最高优先级。.normal
是默认优先级。.fetcher
用于内部RequestEnhacer
,不应由其他程序使用。.max
仅当您需要先于内部RequestEnhancer
时使用。建议在必要的情况下仅使用.custom
。
extension RequestEnhancerPriority {
var less: RequestEnhancerPriority
var more: RequestEnhancerPriority
}
这些修改器允许您指定一个RequestEnhancer
应该在另一个之前运行(这是一个方便的方式来指定顺序,而无需关心顺序或不相关的增强器)。它们也是priority
之所以是静态的的原因。在这个例子中,始终运行RequestEnhancer2
在RequestEnhancer1
之前。
struct RequestEnhancer1: RequestEnhancer {
func enhance(request: inout Request) {
...
}
}
struct RequestEnhancer2: RequestEnhancer {
static let priority: RequestEnhancerPriority = RequestEnhancer1.priority.more
func enhance(request: inout Request) {
...
}
}
请求修饰器
请求增强器
用于所有由Fetcher
实例发出的请求。RequestModifier
是一种可添加到每个单独请求的标记协议。然后RequestEnhancer
可以查看Request.modifiers
以查看是否存在实例并据此采取行动。例如,RequestLogger
的行为由RequestLogging
指定,它是一个包含一些额外数据的RequestModifier
。
向请求添加RequestModifier
有三种方法
register
方法在Fetcher
中(参见Fetcher
)。RequestModifier
应用于所有请求。EndpointProvider
中的implicitModifiers
(参见EndpointProvider
)。RequestModifier
应用于使用由EndpointProvider
的create
方法创建的Endpoint
所创建的所有请求。Endpoint
的init
中的modifiers
参数(参见Endpoint
)。RequestModifier
仅应用于使用此特定Endpoint
实例的请求。
实现
BaseUrl
BaseUrl
是RequestModifier
,它告诉BaseUrlRequestEnhancer
在由Endpoint
指定的URL之前插入哪个URL。BaseUrlRequestEnhancer
默认已注册。BaseUrl
使用RequestEnhancerPriority
决定如果注册了多个,哪一个将使用。init
中baseUrl
参数指定要插入的URL。如果它是nil,则不执行任何操作。有一个特殊的实例BaseUrl.Ignore
,它可以抑制其他BaseUrl
。
示例可以在快速概述中的最后一个示例中找到。
RequestLogger
RequestLogger
是调试请求的好工具。默认情况下,它会将有关请求(和响应)的信息记录到控制台,尽管可以使用init
中的logFunction
来覆盖它。要记录的内容使用RequestLogging
(RequestModifier
)指定。RequestLogging
是OptionSet
,可以通过将它们放入数组来选择多个选项。如果没有RequestLogging
的实例,则使用init
中的defaultOptions
。以下是RequestLogging
的选项:
请求URL
时间
响应代码
请求头
请求体
响应头
响应体
all
- 上面的所有内容disabled
- 没有任何内容
defaultOptions
由requestUrl
、responseCode
和time
组成。
ResponseVerifier
protocol ResponseVerifier: RequestModifier {
func verify(response: Response<SupportedType>) -> FetcherError?
}
verify
由ResponseVerifierEnhancer
(默认已注册)调用。ResponseVerifierEnhancer
获取所有ResponseVerifier
的实例并调用verify
方法。第一个非nil的结果用于通过Response.flatMap
修改响应。只有当Response.result
为成功时才执行动作。
StatusCodeResponseVerifier
是一个预实现的ResponseVerifier
,它将响应状态码与从init
中获得的代码进行比较。如果没有找到匹配项,则返回.invalidStatusCode
,否则返回nil。
输入编码
InputEncoding
是一个标记协议,说明了应该如何将数据编码到Request
和从Response
解码。每个Endpoint
都有一个默认编码,基于它所使用的方法(例如,GET
将数据编码到URL,而POST
则编码到请求体等),但可以在init
中通过传递不同的编码来改变它(参见Endpoint
)。
InputEncodingWithEncoder
protocol InputEncodingWithEncoder: InputEncoding {
func encode(input: SupportedType, to request: inout Request)
}
InputEncodingWithEncoder
是InputEncoding
的扩展。通常DataEncoder
负责根据InputEncoding
(参见DataEncoder)来编码输入数据。使用该协议时,会调用encode
方法来完成工作。
StandardInputEncoding
enum StandardInputEncoding: InputEncoding {
case queryString
case httpBody
}
StandardInputEncoding
表示所有DataEncoder
都理解的两种编码。
.queryString
将输入数据编码为请求URL(例如http://url.xxx?param1=value
)。如在快速概述中的示例所示,这也可以手动完成。.queryString
的缺点是输入数据必须是字典。
.httpBody
简单地将数据发送到请求体中。编码取决于DataEncoder
实现(可能为JSON、XML等)。
RequestPerformer
protocol RequestPerformer {
var dataEncoder: DataEncoder { get }
func perform(request: Request, callback: @escaping (Response<Data>) -> Void) -> Cancellable
}
RequestPerformer
的职责是与服务器通信(执行请求)。这是通过perform
方法完成的,该方法接受Request
并带着Response
调用callback
(这个调用可能不会立即发生)。它返回Cancellable
(取消请求的方式)。
RequestPerformer
还指定了DataEncoder
,这将用于编码和解码数据。
DataEncoder
protocol DataEncoder {
func encodeToQueryString(input: SupportedType, to request: inout Request)
func encodeToHttpBody(input: SupportedType, to request: inout Request)
func encodeCustom(input: SupportedType, to request: inout Request, inputEncoding: InputEncoding)
func decode(response: Response<Data>) -> Response<SupportedType>
}
DataEncoder
在Fetcher
调用它时,用于将输入数据编码到Request
,并从Response
解码数据。
StandardInputEncoding
指定使用哪个encodeToQueryString
或encodeToHttpBody
。
如果使用自定义的InputEncoding
实现,且不是InputEncodingWithEncoder
,则会调用encodeCustom
。默认情况下,调用此方法将导致崩溃(InputEncoding
未知)。
实现
AlamofireRequestPerformer
AlamofireRequestPerformer
是基于 Alamofire 的实现。默认情况下它使用 AlamofireJsonDataEncoder
(JSON 编码器)作为 DataEncoder
,但可以在它的 init
中进行更改。
AlamofireJsonDataEncoder
了解一种特殊的 InputEncoding
类型,即 FormInputEncoding
,它支持内容类型 application/x-www-form-urlencoded
。
请求头
protocol Header: RequestModifier {
var name: String { get }
var value: String { get }
}
Header
是 RequestModifier
的一种特殊类型,它代表请求的头部。注册的 Header
实例通过内部 RequestEnhancer
添加到 Request
中。
预定义的头部
Headers
收集所有预定义的头部。它们通过扩展添加为嵌套的结构体。例如,这是 Accept
的实现
extension Headers {
struct Accept: Header {
let name = "Accept"
let value: String
init(value: String) {
self.value = value
}
}
}
extension Headers.Accept {
static let applicationJson = Headers.Accept(value: "application/json")
static let textPlain = Headers.Accept(value: "text/plain")
}
其他头部也可以用同样的方式添加(以及特定头部的值)。
目前有这些头部: Accept
、ContentType
、Charset
。
Custom
是一个特殊的头部类型。如果某些头部只需要在单个地方使用,则可以使用它,不需要创建一个新的结构体。
FetcherError
enum FetcherError: Error {
case requestError(Error)
case invalidStatusCode
case nilValue
case custom(Error)
case unknown
}
FetcherError
用作 Response.result
中的错误类型。
ErrorHandler
protocol ErrorHandler {
func canResolveError(response: Response<SupportedType>) -> Bool
func resolveError(response: Response<SupportedType>, callback: (Response<SupportedType>) -> Void)
}
ErrorHandler
告诉 Fetcher
如果 Response.result
失败时应该做什么。它可以在其 init
中通过名为 errorHandler
的参数设置。默认的是 NoErrorHandler
。
在解决错误时,Fetcher
首先调用 canResolveError
。如果它返回 false
,则行为没有变化,并且使用未更改的 Response
调用回调。如果返回 true
,则调用 resolveError
而不是回调。然而,回调被传递为一个闭包,您可以用修改后的 Response
调用它。
BaseStatusCodeErrorHandler
BaseStatusCodeErrorHandler
是一个实现用于根据响应的状态码解决错误的“抽象”类。它具有与 StatusCodeResponseVerifier
类似的 init
方法。canResolveError
如果状态码来自 init
中的代码,则返回 true
。
CompositeErrorHandler
struct CompositeErrorHandler: ErrorHandler {
init(handlers: [ErrorHandler])
init(handlers: ErrorHandler...)
}
CompositeErrorHandler
允许多个 ErrorHandler
的组合。仅使用能够解决错误的第一个 ErrorHandler
实际解决错误。
实现
NoErrorHandler
NoErrorHandler
不做任何事(canResolveError
总返回 false
)。
NoInternetErrorHandler
final class NoInternetErrorHandler: BaseStatusCodeErrorHandler {
init(maxRepetitions: Int = 3, delay: DispatchTimeInterval = .seconds(1))
}
NoInternetErrorHandler
通过调用 retry
(见 retry)处理状态码 599
。可以在 init
中设置 retry
的参数。
RequestTimeOutErrorHandler
final class RequestTimeOutErrorHandler: BaseStatusCodeErrorHandler {
init(maxRepetitions: Int = 3, delay: DispatchTimeInterval = .seconds(0))
}
RequestTimeOutErrorHandler
通过调用 retry
(见 retry)处理状态码 408
。可以在 init
中设置 retry
的参数。
RxFetcher
您可以用 Observable
以更“响应式”的方式发出请求,而不使用回调。为此,您需要一个 RxFetcher
实例,该实例可以通过 Fetcher
的 rx
属性获取。与原始方法一样,RxFetcher
提供了相同的 request
方法,但它们返回 Observable
。修改了快速概览中的第三个示例。
let fetcher = Fetcher(requestPerformer: AlamofireRequestPerformer())
fetcher.register(requestEnhancers: HttpBinResponseTranslation())
fetcher.register(requestModifiers: BaseUrl(baseUrl: "https://httpbin.org"))
fetcher.rx.request(Endpoints.get(id: "1", text: "a").subscribe(onNext: { response in
switch response.result {
case .success(let value):
// value was already transformed with DataMapper.
print(value)
case .failure(let error):
// Handle error
break
}
}).addDisposableTo(...)
可观测扩展
extension ObservableConvertibleType where E: ResponseProtocol {
func retryRequest(max: Int = Int.max, delay: DispatchTimeInterval = .seconds(0)) -> Observable<E>
func asResult() -> Observable<FetcherResult<E.T>>
}
retryRequest
是Request
的retry
的响应式变体(参见retry
)。
asResult
返回一个映射的序列,包含Response.result
。
线程安全
Fetcher
默认在后台线程执行许多操作,因此需要是线程安全的。这也适用于像RequestPerformer
、RequestEnhancer
、ErrorHandler
等对象。通常,这些协议的所有方法都是作为纯函数实现的,因此不会引起任何问题。另请参阅DataMapper#ThreadSafety。
版本控制
此库使用语义版本控制。在版本1.0之前,即使是次要版本也可能出现API重大更改。我们考虑版本0.1为预发布版本,这意味着API应该稳定,但尚未在实际项目中测试。在测试后,我们将进行必要的调整,并将版本提升到1.0(第一次发布)。
作者
- Tadeas Kriz,[email protected]
- Filip Dolník,[email protected]
使用的库
AlamofireRequestPerformer
RxFetcher
Tests
许可协议
Fetcher可在MIT 许可协议下使用。