CombineRequest 是一个灵活的框架,用于构建与 API 通信的一系列请求。
通过 Swift Package Manager 进行安装。 将此仓库的 URL 粘贴到 Xcode 中,或将此行添加到您的 Package.swift
文件中。
.package(url: "https://github.com/lightyear/CombineRequest", from: "1.0.0")
此软件包提供了两种主要类型:Request
和 APIBase
。
Request
是一个协议,描述了 API 请求的基本要素。 它定义了 HTTP 方法、端点路径、您期望接收的数据类型以及任何可能的错误(通常只是 Error
)。 从 JSONPlaceholder 获取用户的示例代码如下所示:
class UsersRequest: APIBase, Request {
override init() {
super.init()
path = "https://jsonplaceholder.typicode.com/users"
}
func start() -> AnyPublisher<Data, Error> {
super.sendRequest()
.map { $0.data }
.eraseToAnyPublisher()
}
}
cancellable = UsersRequest()
.start()
.catch {
// error handling
}
.sink {
// $0 is a Data instance with the response JSON
}
APIBase
是另一种类型。 它包含一个 URLSession
实例,构建 URLRequest
并启动数据任务。 它旨在被子类化,并包含给定 API 的所有请求的通用逻辑。 同样,对于 JSONPlaceholder,子类可能如下所示:
class JSONPlaceholderAPI: APIBase {
override init() {
super.init()
baseURL = URL(string: "https://jsonplaceholder.typicode.com")
}
override func buildURLRequest() -> URLRequest? {
var urlRequest = super.buildURLRequest()
urlRequest?.setValue("application/json", forHTTPHeaderField: "Accept")
return urlRequest
}
override func startRequest() -> AnyPublisher<DataResponseTuple, Error> {
super.startRequest()
.validateStatusCode(in: 200..<300)
.hasContentType("application/json")
.eraseToAnyPublisher()
}
}
此子类确保为每个请求设置 Accept
标头,并验证响应的 HTTP 状态代码和内容类型。 请注意,只有叶子类才符合 Request
。 这一点很重要,因为 Swift 不会进一步向下查找继承层次结构以找到属性或函数的正确实现。
从请求返回 Data
blob 不如结构化数据有用。 可以稍微修改 UsersRequest
以自动执行此操作
struct User: Codable {
var id: Int
var name: String
var username: String
var email: String
// etc...
}
class UsersRequest: JSONPlaceholderAPI, Request {
override init() {
super.init()
path = "/users"
}
func start() -> AnyPublisher<[User], Error> {
super.sendRequest()
.decode(type: [User].self, decoder: JSONDecoder())
.eraseToAnyPublisher()
}
}
start()
的返回类型已更改以反映解码后的类型,并且 decode
运算符用于将 Data
解析为 Array<User>
。
有几个有用的运算符可用于验证响应数据是否与您期望的匹配。
如果响应状态代码不是提供的序列,则 validateStatusCode(in:)
会生成一个错误,从而导致管道失败。 您可以传递任何 Int
的 Sequence
(因此,Range<Int>
、Set<Int>
、Array<Int>
都可以)。
如果响应内容类型与传递的类型不匹配,则 hasContentType(_:)
会生成一个错误。 此运算符将匹配带或不带尾随字符集的内容类型。 例如,hasContentType("text/plain")
接受 "text/plain"(完全匹配)或 "text/plain; charset=utf-8" 的内容类型。
您可以使用任何连接到 Apple URL 加载系统的库来测试您的 Request
一致性,例如 OHHTTPStubs。
另一种选择是利用 Combine。 APIBase
公开了 dataTaskPublisher
属性,该属性通常在您的代码调用 sendRequest()
时延迟创建。 如果您将自己的 publisher 分配给此属性,则可以使 URL 加载系统短路并立即生成响应或错误。 APIBase
中有一些辅助函数
stub(with: HTTPURLResponse, data: Data)
创建一个 publisher,该 publisher 生成一个 (data, response)
元组并完成。 此 stub 使您可以最灵活地构建您的代码期望的响应。
stubResponse(statusCode: Int, data: Data, headers: [String: String])
stubJSONResponse(statusCode: Int, data: Data, headers: [String: String])
创建一个 publisher,该 publisher 生成一个 (data, response)
元组并完成。 这两个 stub 负责处理一些样板代码:将正确的 URL 放入响应中,包括 Content-Length
和(对于 JSON 版本)Content-Type
标头。
stub(error: Error)
创建一个 publisher,该 publisher 立即因提供的错误而失败。 如果您想测试网络级别的故障(没有 Internet 连接、DNS 故障等),那么这就是您想要的 stub。 如果您想测试 4xx 和 5xx HTTP 故障,从网络的角度来看,这些实际上是非错误情况,因此您将使用上面的其中一个 stub 并带有适当的状态代码。
您可以在此存储库的测试套件中看到两种测试方法的示例。