褪黑素 0.1.5

褪黑素 0.1.5

the-braveknight 维护。



褪黑素 0.1.5

  • the-braveknight

褪黑素

褪黑素(曾是 SwiftyNetworking)库是一个 Swift 编写的网络库,它提供了一种基于协议的方法来加载数据请求。它提供了一个 Endpoint 协议来确保网络请求以通用和类型安全的方式解析。早期更改可以在不再更新的 SwiftyNetworking 库中追踪。

Endpoint 协议

遵循 Endpoint 协议很简单且直接。这是协议体的样子

public protocol Endpoint {
    associatedtype Response
    
    var scheme: Scheme { get }
    var host: String { get }
    var port: Int? { get }
    var path: String { get }
    var method: HTTPMethod { get }
    var queries: [URLQuery] { get }
    var headers: [HTTPHeader] { get }
    func prepare(request: inout URLRequest)
    func parse(data: Data, urlResponse: URLResponse) throws -> Response
}

该库包含了一些变量和函数的默认实现,以提高便利性。

构建 URLRequest

任何遵循 Endpoint 的对象将自动获取 urlrequest 属性,这将被 URLSession 用于加载请求。

如果您需要修改在加载之前请求,可以实现 prepare(request:) 方法。

@Query 属性包装器

@Query 属性包装器用于声明任何作为 URL 查询的属性。您端点的体内使用 @Query 声明的所有属性都将添加到最终的 URL 中。

struct APIEndpoint: Endpoint {
    ...
    @Query(name: "name") var name: String? = "the-braveknight"
    @Query(name: "age") var pageNumber: Int? = 2
    ...
}

在上面的代码中,URL 查询将如下所示: ?name=the-braveknight&pageSize=2。您仍然可以通过直接设置端点中的 queries 属性来添加多个查询。

@Header 属性包装器

同样,@Header 属性包装器 用于声明头部,这将在加载之前添加到 URLRequest。库包含多个常用 HTTP 头部,您也可以实现自己的。

struct APIEndpoint: Endpoint {
    ...
    @Header(\.accept) var accept: MIMEType = .json
    @Header(\.contentType) var contentType: MIMEType = .json
    ...
}

同样,您一次仍然可以添加多个头部,直接设置端点中的 headers 属性。

@QueryGroup & @HeaderGroup

这些结果构建器允许您分别构建查询项和头部的数组。例如,我们可以使用它们来构建端点的 queriesheaders 数组。

struct APIEndpoint: Endpoint {
    ...
    @HeaderGroup var headers: [HTTPHeader] {
        Accept(.json)
        ContentType(.json)
    }
    
    @QueryGroup var queries: [URLQuery] {
        Query(name: "name", value: "the-braveknight")
        Query(name: "age", value: 2)
    }
    ...
}

解码响应

在某些情况下,例如当 Response 遵循 Decodable 且我们期望解码 JSON 时,为 parse(data:urlResponse:) 方法提供默认实现来处理该操作是合理的。

public extension Endpoint where Response : Decodable {
    func parse(data: Data, urlResponse: URLResponse) throws -> Response {
        let decoder = JSONDecoder()
        return try decoder.decode(Response.self, from: data)
    }
}

您仍然可以提供此方法的自己的实现来覆盖此实现。

一个示例端点

这是一个使用 GET 方法的示例端点,用于解析来自 Agify.io API 的请求。

API 调用响应体(https://api.agify.io/?name=bella)如下所示

{
    "name" : "bella",
    "age" : 34,
    "count" : 40138
}

可以包含此数据的自定义 Swift 结构如下所示

struct Person : Decodable {
    let name: String
    let age: Int
}

最后,这是我们的端点看起来像什么样式

struct AgifyAPIEndpoint : Endpoint {
    typealias Response = Person
    
    let host: String = "api.agify.io"
    let path: String = "/"
    @Query(name: "name") var name: String? = nil
    @Header(\.accept) var accept: MIMEType = .json
}

我们可以使用 Swift 的点语法来使其调用端点更加方便。

extension Endpoint where Self == AgifyAPIEndpoint {
    static func estimatedAge(forName personName: String) -> Self {
        AgifyAPIEndpoint(name: personName)
    }
}

最后,这是我们调用端点的方式。结果是 Result<Person, Error> 类型。

URLSession.shared.load(.estimatedAge(forName: "Zaid")) { result in
    do {
        let person = try result.get()
        print("\(person.name) is probably \(person.age) years old.")
    } catch {
        // Handle errors
    }
}

Combine

褪黑素支持使用 Combine 框架加载数据。

let subscription: AnyCancellable = URLSession.shared.load(.estimatedAge(forName: "Zaid"))
    .sink { completion in
        // Handle errors
    } receiveValue: { person in
        print("\(person.name) is probably \(person.age) years old.")
    }

Swift 并发

美拉酮还支持使用 Swift Concurrency 和 async/await 来加载端点。

Task {
    do {
        let person = try await URLSession.shared.load(.estimatedAge(forName: "Zaid"))
        print("\(person.name) is probably \(person.age) years old.")
    } catch {
        // Handle errors
    }
}

致谢