Endpoints 是基于 URLSession
的一个轻量级网络抽象层,它能让你在几秒钟内启动并运行你的网络代码。
import Endpoints
struct MyEndpoint: Endpoint {
let baseURL: URL = URL(string: "https://mysite.com/api")!
let path: String = "/"
let method: HTTPMethod = .get
}
let endpoint = MyEndpoint()
let communicator = Communicator()
communicator.performRequest(to: endpoint) { result in
switch result {
case .success(let response):
// Yay!
break
case .failure(let error):
// Nay...
break
}
}
在许多情况下,你可能对请求的实际响应数据感兴趣。Endpoints 使用关联的 ResponseType
类型,使解码 Swift struct
变得简单。
// Define your model
struct MyModel: Decodable {
let id: Int
let name: String
}
struct MyEndpoint: Endpoint {
// Declare a ResponseType in your endpoint
typealias ResponseType = MyModel
let baseURL: URL = URL(string: "https://mysite.com/api")!
let path: String = "/mymodel/1"
let method: HTTPMethod = .get
}
communicator.performRequest(to: endpoint) { result in
switch result {
case .success(let response):
// response.body contains an instance of MyModel
break
case .failure(let error):
break
}
}
默认情况下,Endpoints 假定 JSON 解码。如果你需要更改默认的解码行为,请查看“通过扩展进行自定义”部分。
Endpoints 还内置支持 Apple 的 Combine 框架。
communicator.publisher(for: endpoint)
.sink(
receiveCompletion: { completion in
// Handle successful completion or failure
},
receiveValue: { response in
// Handle a successful response here
}
)
如果你需要更改端点的默认解码行为,你可以简单地覆盖默认实现。
extension Endpoint where ResponseType: Decodable {
func unpack(data: Data) throws -> ResponseType {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode(ResponseType.self, from: data)
}
}
如果你想混合搭配,你可以保持默认设置不变,并在你的 Endpoint 实现中覆盖 unpack(data:)
函数以提供自定义解码。
struct MyEndpoint: Endpoint {
typealias ResponseType = MyModel
let baseURL: URL = URL(string: "https://mysite.com/api")!
let path: String = "/mymodel/1"
let method: HTTPMethod = .get
func unpack(data: Data) throws -> MyModel {
let decoder = JSONDecoder.myDecoder
return try decoder.decode(MyModel.self, from: data)
}
}
在许多情况下,你希望为所有端点使用单个 baseURL
,你可以通过扩展 Endpoint
协议来实现。
extension Endpoint {
var baseURL: URL {
return URL(string: "https://mysite.com/api")!
}
}
Communicator
类依赖于 Transporter
协议来完成繁重的工作。这是一个包含单个函数的简单协议。
public protocol Transporter {
func send(
_ request: URLRequest,
completionHandler: @escaping (Result<TransportationResult, CommunicatorError>) -> Void
) -> Request
}
默认情况下,Endpoints 扩展了 URLSession
以符合此协议,然后使用它来执行实际的网络请求。 这允许你创建自己的自定义 Transporter
,例如用于身份验证。
class AuthorizationTransporter: Transporter {
private let base: Transporter
private let authenticationDetails: String
init(base: Transporter, authenticationDetails: String) {
self.base = base
self.authenticationDetails = authenticationDetails
}
func send(
_ request: URLRequest,
completionHandler: @escaping (Result<TransportationResult, CommunicatorError>) -> Void
) -> Request {
var modifiedRequest = request
modifiedRequest.addValue("Basic \(authenticationDetails)", forHTTPHeaderField: "Authorization")
return base.send(modifiedRequest, completionHandler: completionHandler)
}
}
let authTransporter = AuthorizationTransporter(base: URLSession.shared, authenticationDetails: "...")
let communicator = Communicator(transporter: authTransporter)
你可能需要测试基于 Endpoints 构建的代码。 为了帮助你,Endpoints 包含了 EndpointsTesting 包,它在测试时为你提供有用的类。 下面是一个示例代码片段,展示了它的用法。
import EndpointsTesting
class MyTestCase: XCTestCase {
func testMyCode() {
// The TestTransporter class can enqueue responses, that are responded with in FIFO-order
// (first-in first-out). This allows you to set up a chain of responses.
let testTransporter = TestTransporter(responses: [
.success(.init(code: 200, data: MyTestFixture.sampleData))
])
// When setting up the Communicator, simply pass in the TestTransporter
let communicator = Communicator(transporter: testTransporter)
// Done! Here you would typically put your test code
}
}
要开始使用 Endpoints,只需将其添加为 Swift Package 依赖项。
.package(url: "https://github.com/isotopsweden/Endpoints", from: "3.0.0")