Apexy

CocoaPods Compatible Platform SPM compatible Swift 5.3 GitHub license codebeat badge

用于组织项目网络层的库。

安装

CocoaPods

要使用 CocoaPods 将 Apexy 集成到您的 Xcode 项目中,请在您的 Podfile 中指定它。

如果您想将 Apexy 与 Alamofire 一起使用

pod 'Apexy'

如果您想在没有 Alamofire 的情况下使用 Apexy

pod 'Apexy/URLSession'

如果您想使用 ApexyLoader

pod 'Apexy/Loader'

Swift Package Manager

如果您有 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 (端点)

Endpoint - 用于组织 REST API 工作的基本协议之一。 它是一组请求和响应处理。

必须不可变。

  1. 创建 URLRequest 以发送请求。
  2. 验证服务器响应中的 API 错误。
  3. 将服务器响应转换为正确的类型 (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 (客户端)

Client - 一个只有一个方法来执行 Endpoint 的对象。

ClientEndpoint 分离允许您将 Client 中的异步代码与 Endpoint 中的同步代码分离。 因此,副作用在 Client 中隔离,而纯函数在不可变的 Endpoint 中。

CombineClient

CombineClient - 将网络请求包装在 Combine 中的协议。

ConcurrencyClient

ConcurrencyClient - 将网络请求包装在 Async/Await 中的协议。

ClientCombineClientConcurrenyClient 是分离的协议。您可以通过使用特定协议来指定您正在使用的方法。

入门

由于大多数请求都会收到 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

将网络层分成文件夹

最终的文件和文件夹结构

要求

其他资源