概述

轻巧且令人愉悦的网络库。

Atom 是一个封装库,围绕 URLSession 提供的一部分功能构建,并增加了将数据解码为模型、代表客户端处理访问令牌刷新和授权标头等功能。它利用 Swift 的特性,例如协议的默认实现、泛型和 Decodable,使其极其容易集成到现有项目中并使用。Atom 支持任何端点、更严格的 URL 主机和路径验证、全面的文档以及示例应用程序,以消除任何猜测。

功能特点

要求

安装

Swift 包管理器

Swift 包管理器是一个用于自动化 Swift 代码分发的工具,并已集成到 swift 编译器中。

一旦您设置了 Swift 包,将 Atom 添加为依赖项就像将其添加到 Package.swiftdependencies 值一样简单。

如果您正在使用 Xcode,添加 Atom 作为依赖项甚至更容易。首先,选择您的应用程序,然后选择您的应用程序项目。在项目编辑器顶部看到 Swift Packages 选项卡后,单击它。单击 + 按钮并添加以下 URL

https://github.com/alaskaairlines/atom/

此时,您可以设置您的项目以使用软件包的分支或标记版本。

用法

入门很容易。首先,创建 Atom 的一个实例。

let atom = Atom()

在上面的示例中,将使用默认配置。此配置设置 URLSession 以使用临时会话,并确保在调用基于完成的回调 API 时,服务返回的数据在主线程上可用。

当使用 async/await API 时,Atom 将在 URLSession 返回数据的同一线程上返回结果。您可以自由使用自定义 actor 或将 @MainActor 属性应用于函数或整个类型(例如,ViewModel),以确保操作在主线程上运行。

任何端点都需要遵循并实现 Requestable 协议。Requestable 协议为其所有属性提供了默认实现 - 除了 func baseURL() throws(AtomError) -> BaseURL。请参阅文档以获取更多信息。

extension Seatmap {
    enum Endpoint: Requestable {
        case refresh

        func baseURL() throws(AtomError) -> BaseURL {
            try BaseURL(host: "api.alaskaair.net")
        }
    }
}

Atom 提供了许多方法,支持完全解码的模型对象、原始数据或指示请求成功/失败的状态。

typealias Endpoint = Seatmap.Endpoint

let seatmap = try await atom.enqueue(Endpoint.refresh).resume(expecting: Seatmap.self)

上面的示例演示了如何使用 resume(expecting:) 函数来获取完全解码的 Seatmap 模型对象。

有关更多信息,请参阅文档

身份验证

Atom 可以配置为代表客户端应用授权标头。它支持 BasicBearer 两种身份验证方法。正确配置后,如果 Atom 确定访问令牌已过期,它将自动为客户端刷新令牌。

但是,如果令牌刷新尝试失败,所有后续的网络调用都将失败。

Basic

您可以配置 Atom 应用 Basic 授权标头,如下所示

let atom: Atom = {
    let credential = BasicCredential(password: "password", username: "username")
    let basic = AuthenticationMethod.basic(credential)
    let configuration = ServiceConfiguration(authenticationMethod: basic)

    return Atom(serviceConfiguration: configuration)
}()

可以通过遵循并实现 BasicCredentialConvertible 协议来扩展现有实现。一个假设的配置可能如下所示

actor CredentialManager {
    private(set) var username = String()
    private(set) var password = String()

    static let shared = CredentialManager()
    private init() {}

    func update(username aUsername: String) {
        username = aUsername
    }

    func update(password aPassword: String) {
        password = aPassword
    }
}

extension CredentialManager: BasicCredentialConvertible {
    var basicCredential: BasicCredential {
        .init(password: password, username: username)
    }
}

let atom: Atom = {
    let basic = AuthenticationMethod.basic(CredentialManager.shared.basicCredential)
    let configuration = ServiceConfiguration(authenticationMethod: basic)

    return Atom(serviceConfiguration: configuration)
}()

配置完成后,Atom 会将用户名和密码组合成一个字符串 username:password,使用 base 64 编码算法对结果进行编码,并将其作为 Authorization: Basic TGlmZSBoYXMgYSBtZWFuaW5nLg== 标头键值应用于请求。

Bearer

您可以配置 Atom 应用 Bearer 授权标头。这是一个例子

actor TokenManager: TokenCredentialWritable {
    var tokenCredential: TokenCredential {
    	// Read values from the keychain.
        get { keychain.tokenCredential() }
        
        // Save new value to the keychain.  
        set { keychain.save(tokenCredential: newValue)  }
    }
}

let atom: Atom = {
    let endpoint = AuthorizationEndpoint(host: "api.alaskaair.net", path: "/oauth2")
    let clientCredential = ClientCredential(id: "client-id", secret: "client-secret")
    let tokenManager = TokenManager()

    let bearer = AuthenticationMethod.bearer(endpoint, clientCredential, tokenManager)
    let configuration = ServiceConfiguration(authenticationMethod: bearer)

    return Atom(serviceConfiguration: configuration)
}()

希望设置很容易理解。Atom 需要客户端提供一些信息

  1. 授权端点 - Atom 需要知道在哪里调用以获取新令牌。
  2. 客户端凭据 - Atom 需要访问客户端 ID 和客户端密钥才能获取新令牌。
  3. 令牌凭据可写 - Atom 会将新获得的凭据传递给客户端以进行安全存储。

配置完成后,Atom 会将授权标头作为 Authorization: Bearer ... 标头键值应用于请求。

注意: 请确保任何符合 TokenCredentialWritable 的类型都以线程安全的方式写入和读取密钥链值。成功的令牌刷新取决于在刷新后将新令牌凭据值保存到密钥链后能够读取它。

此外,Atom 只会从以此形式返回的 JSON 对象中解码令牌凭据

{
    "access_token": "2YotnFZFEjr1zCsicMWpAA",
    "expires_in": 3600,
    "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA"
}

上面的响应符合 RFC 6749,第 1.5 节

有关更多信息和 Atom 用法示例,请参阅文档和提供的示例应用程序。

沟通

作者