用于组织项目网络层的库。
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。JsonEndpointVoidEndpointBookListEndpointBookEndpointUpdateBookEndpointDeleteBookEndpointAPIErrorBook
BookListEndpointTestsBookEndpointTestsUpdateBookEndpointTestsDeleteBookEndpointTests