SwiftyNetworking

请注意 - 此软件包正处于大量开发中!👨🏻‍💻 🚀

概述

SwiftyNetworking 是一个简单的软件包,它支持网络层,并提供类似于 SwiftUI 的 ViewBuilder 的请求构建模式。

注意: 该软件包正在进行大量开发。类型和方法的结构似乎已最终确定,但随着时间的推移,可能会因需要实现新功能而进行一些更改。0.8 版本非常接近最终版本。在此之前,我想添加模拟和测试,以完成我希望在此软件包中拥有的所有内容。

如何使用?

  1. 创建提供相关 API 的服务。
struct ExampleService: Service {
    var baseURL: URL { URL(string: "https://www.example.com")! }
}
  1. 数据错误 响应准备模型。
struct ExampleResponseModel: Codable {
    let foo: String
    let bar: Int
    let buzz: Bool
}

struct ExampleErrorModel: Codable {
    let status: Int
    let message: String
}
  1. 使用 Request 宏描述请求。
@Request
struct ExampleRequest {

    let bar: String
    
    var body: some Request {
        Get("foo", bar, "buzz", from: ExampleService())
            .headers {
                X_Api_Key(value: "sample_token")
            }
            .queryItems {
                Key("hello", value: "world")
            }
            .body {
                Key("array") {
                Key("int", value: 42)
                Key("double", value: 3.14)
                Key("bool", value: true)
                Key("string", value: "foo")
                Key("array", value: ["foo", "bar", "buzz"])
            }
            .responseBody(ExampleResponseModel.self)
            .responseError(ExampleErrorModel.self)
        }
    }
}
  1. 创建 session 并发送请求。当然,您可以根据需要取消它。😉
let session = Session()
let (result, error) = await session.send(request: ExampleRequest(bar: "buzz"))

if sometingIsWrong {
    session.cancel(requests: .only(request.id))
}

就是这样!

高级用法

模板

我们喜欢优化我们的工作!这就是我为 Request 的基本实现准备模板的原因之一。另一个原因是发现了令牌菜单!你喜欢这种方法吗?给它一个星星吧!⭐️ Request template 您可以通过运行位于 Templates 目录中的 install.sh 脚本轻松安装模板。

授权

SwiftyNetworking 提供了易于使用且灵活的授权模型。假设大多数授权包括从一个请求中获取令牌并在其他请求中使用它,此软件包包含一个简单的系统,允许您捕获和使用此类值。

  1. AuthorizationService 创建一个新的继承结构。有两个变量和一个方法需要存储敏感数据。最重要的部分是 func authorize<R: Request>(_ request: R) -> R,您可以在其中注入来自存储的令牌。
struct BackendAuthorization: AuthorizationProvider {

    var store: AuthorizationStore = BackendAuthorizationStore()
    var afterAuthorization: ((Codable, AuthorizationStore) -> Void)? = nil
        
    func authorize<R: Request>(_ request: R) -> R {
        if let token = store.get(key: .token) {
            return request.headers {
                Authorization(.bearer(token: token))
            }
        } else {
            return request
        }
    }
}
  1. 您可以使用默认的 KeychainAuthorizationStore 或从 AuthorizationStore 创建一个新的继承结构。
struct BackendAuthorizationStore: AuthorizationStore {
    let keychain = Keychain(service: "com.example.app")
    
    static var tokenKey: String { "com.example.app.token" }
    static var refreshTokenKey: String { "com.example.app.refresh-token" }
    static var usernameKey: String { "com.example.app.username" }
    static var passwordKey: String { "com.example.app.password" }
    
    //I would like to make it better
    func store(key: AuthorizationKey, value: String) {
        try? keychain.set(value, key: key.representant(for: Self.self))
    }
    
    func get(key: AuthorizationKey) -> String? {
        try? keychain.get(key.representant(for: Self.self))
    }
}
  1. 我们已准备好捕获我们的凭据。在这种情况下,它将是服务器在身份验证过程后返回的令牌。
@Request
struct ExampleLoginRequest {    
    var body: some Request {
        Get("login", from: ExampleService())
            //[...]
            .responseBody(LoginResponse.self)
            .afterAutorization { response, store in
                store.value(.token(response.token))
            }
        }
  
  1. authorize() 修饰符添加到每个需要授权的请求。
@Request
struct ExampleAuthorizedRequest {    
    var body: some Request {
        Get("foo", bar, "buzz", from: ExampleService())
            //[...]
            .authorize()
        }
    }
}

就是这样!

中间件

在使用网络层时,我们经常执行重复性操作,例如添加适当的授权标头或希望保存已发送请求的效果。SwiftyNetworking 允许您在完成查询之前和收到响应之后执行操作。

struct ExampleService: Service {

    //[...]
    
    func beforeEach<R>(_ request: R) -> R where R: Request {
        request
            .headers {
                X_Api_Key(value: "secret_token")
            }
    }

    func afterEach<B>(_ response: Response<B>) -> Response<B> where B: Codable {
        statistics.store(response.statusCode)
    }
}

日志记录器

SwiftyNetworking 提供默认的 OSLog 实体。您可以使用自己的 Logger 对象。

import OSLog

struct ExampleService: Service {
    //[...]
    
    let logger = Logger(subsystem: "com.example.app", category: "networking")
}

路线图

下一步是什么?

我希望添加和支持更多内容:

模拟数据

// Dummy code
request
    .mocked(where: { response in
        switch response {
        case successed:
            //do something
        case failed:
            //do something
        }
    })

请求排队

// Dummy code
@Request
struct ExampleRequest {
    var body: some Request {
        Queue {
            Get("/example/1", from: ExampleService())
            Get("/example/2", from: ExampleService())
            Get("/example/3", from: ExampleService())
        }
    }
}

网络预览

// Dummy code
@Request
struct ExampleRequest {
    var body: some Request { }
}

#NetworkingPreview {
    ExampleRequest()
}

支持 curl 字符串

// Dummy code
@Request
struct ExampleRequest {
    typealias Response = ExampleResponseModel
    typealias ResponseError = ExampleErrorModel
    
    var body: some Request {
        "curl -X POST https://www.example/login/ -d 'username=example&password=examle'"
    }
}

更多修饰符,更多设置!