用于组织项目网络层的库。
namespace
中隔离。enum
。要使用 CocoaPods 将 Apexy 集成到您的 Xcode 项目中,请在您的 Podfile 中指定它。
如果您想将 Apexy 与 Alamofire 一起使用
pod 'Apexy'
如果您想在没有 Alamofire 的情况下使用 Apexy
pod 'Apexy/URLSession'
如果您想使用 ApexyLoader
pod 'Apexy/Loader'
如果您有 Xcode 项目,请打开它并选择 File → Swift Packages → Add package Dependency 并粘贴 Apexy 仓库 URL
https://github.com/RedMadRobot/apexy-ios
有 3 个软件包产品:Apexy、ApexyAlamofire、ApexyLoader。
Apexy — 在底层使用 URLSession
ApexyAlamofire — 在底层使用 Alamofire
ApexyLoader — Apexy 的附加组件,用于将获取的数据存储在内存中并观察加载状态。 请参阅文档了解详细信息 ApexyLoader
如果您有自己的 Swift 包,请将 Apexy 作为依赖项添加到 Package.swift 的 dependencies 值中。
dependencies: [
.package(url: "https://github.com/RedMadRobot/apexy-ios.git")
]
Endpoint
- 用于组织 REST API 工作的基本协议之一。 它是一组请求和响应处理。
必须不可变。
URLRequest
以发送请求。Data
, String
, Decodable
)。public struct Book: Codable, Identifiable {
public let id: String
public let name: String
}
public struct BookEndpoint: Endpoint {
public typealias Content = Book
public let id: Book.ID
public init(id: Book.ID) {
self.id = id
}
public func makeRequest() throws -> URLRequest {
let url = URL(string: "books")!.appendingPathComponent(id)
return URLRequest(url: url)
}
public func validate(_ response: URLResponse?, with body: Data) throws {
// TODO: check API / HTTP error
}
public func content(from response: URLResponse?, with body: Data) throws -> Content {
return try JSONDecoder().decode(Content.self, from: body)
}
}
let client = Client ...
let endpoint = BookEndpoint(id: "1")
client.request(endpoint) { (result: Result<Book, Error>)
print(result)
}
Client
- 一个只有一个方法来执行 Endpoint
的对象。
Endpoint
。Combine
中,并且不必为每个请求制作包装器。将 Client
和 Endpoint
分离允许您将 Client
中的异步代码与 Endpoint
中的同步代码分离。 因此,副作用在 Client
中隔离,而纯函数在不可变的 Endpoint
中。
CombineClient
- 将网络请求包装在 Combine
中的协议。
ConcurrencyClient
- 将网络请求包装在 Async/Await
中的协议。
Client
方法的扩展来实现。ApexyAlamofire
使用 Alamofire
中内置的 Async/Await
实现URLSession
,新的 Async/Await
方法使用 URLSession
的 AsyncAwait
扩展实现来实现,适用于 iOS 14 及以下版本。(查看 URLSession+Concurrency.swift
了解更多详情)Client
、CombineClient
和 ConcurrenyClient
是分离的协议。您可以通过使用特定协议来指定您正在使用的方法。
由于大多数请求都会收到 JSON,因此有必要在模块级别制作基本协议。 它们将包含特定 API 的通用请求逻辑。
JsonEndpoint
- 用于等待响应正文中 JSON 的请求的基本协议。
public protocol JsonEndpoint: Endpoint where Content: Decodable {}
extension JsonEndpoint {
public func validate(_ response: URLResponse?, with body: Data) throws {
// TODO: check API / HTTP error
}
public func content(from response: URLResponse?, with body: Data) throws -> Content {
return try JSONDecoder().decode(Content.self, from: body)
}
}
VoidEndpoint
用于不等待响应正文的请求的基本协议。
public protocol VoidEndpoint: Endpoint where Content == Void {}
extension VoidEndpoint {
public func validate(_ response: URLResponse?, with body: Data) throws {
// TODO: check API / HTTP error
}
public func content(from response: URLResponse?, with body: Data) throws {}
}
BookListEndpoint
- 获取书籍列表。
public struct BookListEndpoint: JsonEndpoint, URLRequestBuildable {
public typealias Content = [Book]
public func makeRequest() throws -> URLRequest {
return get(URL(string: "books")!)
}
}
BookEndpoint
- 通过 ID
获取一本书。
public struct BookEndpoint: JsonEndpoint, URLRequestBuildable {
public typealias Content = Book
public let id: Book.ID
public init(id: Book.ID) {
self.id = id
}
public func makeRequest() throws -> URLRequest {
let url = URL(string: "books")!.appendingPathComponent(id)
return get(url)
}
}
UpdateBookEndpoint
- 更新一本书。
public struct UpdateBookEndpoint: JsonEndpoint, URLRequestBuildable {
public typealias Content = Book
public let Book: Book
public func makeRequest() throws -> URLRequest {
let url = URL(string: "books")!.appendingPathComponent(Book.id)
return put(url, body: .json(try JSONEncoder().encode("Book")))
}
}
为了方便构建
URLRequest
,您可以使用HTTP
中的函数。
DeleteBookEndpoint
- 通过 ID
删除一本书。
public struct DeleteBookEndpoint: VoidEndpoint, URLRequestBuildable {
public let id: Book.ID
public init(id: Book.ID) {
self.id = id
}
public func makeRequest() throws -> URLRequest {
let url = URL(string: "books")!.appendingPathComponent(id)
return delete(url)
}
}
您可以使用 UploadEndpoint
发送文件或大量数据。 在 makeRequest()
方法中,您需要返回 URLRequest
和您正在上传的数据,它可以是一个文件 .file(URL)
、一个数据 .data(Data)
或一个流 .stream(InputStream)
。 要执行请求,请调用 Client.upload(endpoint: completionHandler:)
方法。 使用 Progress
对象来跟踪数据上传的进度或取消请求。
public struct FileUploadEndpoint: UploadEndpoint {
public typealias Content = Void
private let fileUrl: URL
init(fileUrl: URL) {
self.fileUrl = fileUrl
}
public func content(from response: URLResponse?, with body: Data) throws {
// ...
}
public func makeRequest() throws -> (URLRequest, UploadEndpointBody) {
var request = URLRequest(url: URL(string: "upload")!)
request.httpMethod = "POST"
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
return (request, .file(fileUrl))
}
}
如果您的应用程序名为 Household
,则网络模块将被称为 HouseholdAPI
。
将网络层分成文件夹
Model
包含网络级别模型的文件夹。 这就是我们发送到服务器以及在响应中得到的内容。Endpoint
包含请求的文件夹。Common
包含常用帮助程序的文件夹,例如 APIError
。JsonEndpoint
VoidEndpoint
BookListEndpoint
BookEndpoint
UpdateBookEndpoint
DeleteBookEndpoint
APIError
Book
BookListEndpointTests
BookEndpointTests
UpdateBookEndpointTests
DeleteBookEndpointTests