Leash 3.2.0

Leash 3.2.0

测试已测试
Lang语言 SwiftSwift
许可证 MIT
发布最后一次发布2020年8月
SPM支持SPM

Luciano Polit维护。



Leash 3.2.0

  • 作者:
  • Luciano Polit

Leash

Build Coverage Swift Package Manager compatible Cocoapods compatible Carthage compatible

索引

简介

您是否使用Alamofire?您是否习惯在每个新项目中创建一些名为 APIManagerAlamofire+NameOfProject 的网络层?您是否习惯于花时间解耦并从前一个实现中提取所需的内容?或者甚至重新编写之前所做的代码?有没有一种解决方案,让您不再处理这个问题?

现在,与其创建网络层,您创建 中间件,它们在不同的生命周期阶段拦截请求(根据需求而定)。有关更多信息,请参阅 中间件

此外,Leash还包括一些网络层常见的流程,例如 编码解码身份验证 等。为了澄清,它还使用了 Alamofire

需求

  • Xcode 10.0+
  • Swift 5.0+

安装

Swift包管理器

要通过Swift包管理器将Leash集成到您的项目中,请将其指定在您的Package.swift文件中。

// swift-tools-version:5.0

import PackageDescription

let package = Package(
    name: "YourPackageName",
    dependencies: [
        .package(
            url: "https://github.com/LucianoPolit/Leash.git",
            .upToNextMajor(from: "3.2.0")
        )
    ],
    targets: [
        .target(
            name: "YourTarget",
            dependencies: [
                "Leash",
                "LeashInterceptors",
                "RxLeash"
            ]
        )
    ]
)

CocoaPods

要通过CocoaPods将Leash集成到您的项目中,请将其指定在您的Podfile文件中。

pod 'Leash', '~> 3.2'
pod 'Leash/Interceptors', '~> 3.2'
pod 'Leash/RxSwift', '~> 3.2'

Carthage

要通过Carthage将Leash集成到您的项目中,请将其指定在您的Cartfile文件中。

github "LucianoPolit/Leash" ~> 3.2

使用方法

配置

首先,我们需要配置一个 Manager。您可以在此处查看所有可用的选项:这里。以下是一个示例:

let manager = Manager.Builder()
    .scheme("http")
    .host("localhost")
    .port(8080)
    .path("api")
    .build()

然后,我们需要一个 Client 来创建和执行请求

let client = Client(
    manager: manager
)

假设我们已经创建了一个包含所有可访问端点的 APIEndpoint,我们可以执行请求。例如:

client.execute(
    APIEndpoint.readAllUsers
) { (response: Response<[User]>) in
    // Do whatever you have to do with the response here.
}

好吧,这很棒。但是,如果我们只想这样调用怎么办呢?

usersClient.readAll { response in
    // Do whatever you have to do with the response here.
}

这要简单得多,不是吗?为了使您的项目尽可能简单且干净,请遵循示例项目的架构。

编码

现在您已经知道如何执行请求,您可能会问如何配置参数(因为接受 Any)。这取决于端点是查询还是 Body 编码

  • 查询类型:QueryEncodable[String: CustomStringConvertible]
  • Body 类型:Encodable[String: Any]

以下是一个包含所有可能性的示例

enum APIEndpoint {
    case first(QueryEncodable)
    case second([String: CustomStringConvertible])
    case third(Encodable)
    case fourth([String: Any])
}

extension APIEndpoint: Endpoint {

    var path: String {
        return "/it/does/not/matter/"
    }

    var method: HTTPMethod {
        switch self {
        case .first: return .get
        case .second: return .get
        case .third: return .post
        case .fourth: return .post
        }
    }

    var parameters: Any? {
        switch self {
        case .first(let request): return request // This is `QueryEncodable`.
        case .second(let request): return request // This is `[String: CustomStringConvertible]`.
        case .third(let request): return request // This is `Encodable`.
        case .fourth(let request): return request // This is `[String: Any]`.
        }
    }
    
}

使用三个不同的类来编码这些参数

如果您想以不同的方式编码参数,您需要重写方法 Client.urlRequest(for>:

解码

在执行请求时,您必须指定响应类型。此外,此类型必须遵循 Decodable。执行请求后,如果响应成功,您将拥有指定类型的响应值。所以,一个简单的示例可以是:

client.execute(
    APIEndpoint.readAllUsers
) { (response: Response<[User]>) in
    // Here, in case of a successful response, 
    // the `response.value` is of the type `[User]`.
}

将响应序列化的一种方法是使用 JSONDecoder。如果您需要使用不同的方法,您应实现自己的 响应序列化器。是的,它使用了由 Alamofire 提供的 DataResponseSerializerProtocol

例如,下面是如何实现一个JSON响应处理器的示例:

extension DataRequest {

    @discardableResult
    func responseJSON(
        client: Client,
        endpoint: Endpoint,
        completion: @escaping (Response<Any>) -> Void
    ) -> Self {
        return response(
            client: client,
            endpoint: endpoint,
            serializer: JSONResponseSerializer(),
            completion: completion
        )
    }

}

为了便于执行请求的过程,您应该在Client中添加类似的方法:

extension Client {

    @discardableResult
    func execute(
        _ endpoint: Endpoint, 
        completion: @escaping (Response<Any>) -> Void
    ) -> DataRequest? {
        do {
            return request(for: endpoint)
                .responseJSON(
                    client: self, 
                    endpoint: endpoint, 
                    completion: completion
                )
        } catch {
            completion(
                .failure(
                    Error.encoding(error)
                )
            )
            return nil
        }
    }

}

这些扩展的结果示例可能是:

client.execute(
    APIEndpoint.readAllUsers
) { (response: Response<Any>) in
    // Here, in case of a successful response, 
    // the `response.value` is of the type `Any`.
}

现在,您可以创建自己的DataResponseSerializer并使用Leash的所有功能了!

认证者

需要认证请求吗?这非常简单,让我们来试试!

class APIAuthenticator {

    var accessToken: String?

}

extension APIAuthenticator: Authenticator {

    static var header: String = "Authorization"

    var authentication: String? {
        guard let accessToken = accessToken else { return nil }
        return "Bearer \(accessToken)"
    }

}

然后,您只需注册认证者:

let authenticator = APIAuthenticator()
let manager = Manager.Builder()
    { ... }
    .authenticator(authenticator)
    .build()

现在,您的所有请求已经过认证。但是,您是否在担心令牌过期的问题?这里有为这一问题提供的解决方案!

拦截器

现在,是时候展示这个框架中最强大的工具了。拦截器(Interceptors)允许您在请求生命周期中的不同时刻拦截请求。有五个不同的时刻,以下是需要明确说明的:

  • 执行:在请求执行之前调用。
  • 失败:在请求执行过程中出现问题时调用。
  • 成功:在请求执行没问题时调用。
  • 完成:在完成处理器之前调用。
  • 序列化:在序列化操作后调用。

在每个请求中都会调用三种类型的拦截器(执行、失败或成功、完成)。此外,还有一个类型,根据您是否序列化响应来调用(序列化)。

Manager可以存储您需要的任何数量的拦截器。在调用时刻,它按队列顺序异步调用每个拦截器(它们被添加的相同顺序)。如果一个类型完成请求以请求完成操作,则不会调用该类型的更多拦截器。

一个最好的优点是,拦截器之间互不依赖。因此,您可以在任何时刻(无需处理编译问题)将其中的一个提取到您的项目中。更重要的是,您可以将这些组件中的任何一个用于其他项目,无需进行任何更改!

以下我会用一些示例来向您展示如何与不同类型的拦截器进行交互。有更多用例,这取决于您和您的需求!

再次提醒,别忘了像这样将拦截器添加到Manager中:

let manager = Manager.Builder()
    { ... }
    .add(
        interceptor: Interceptor()
    )
    .build()

执行

拦截器的目的是在请求执行之前进行拦截。让我给你举两个例子

首先,最简单的一个,我们需要记录每次执行的请求

class LoggerInterceptor: ExecutionInterceptor {

    func intercept(
        chain: InterceptorChain<Data>
    ) {
        defer { chain.proceed() }
        guard let request = try? chain.request.convertible.asURLRequest(),
            let method = request.httpMethod,
            let url = request.url?.absoluteString 
            else { return }

        Logger.shared.logDebug("👉👉👉 \(method) \(url)")
    }
    
}

现在,一个更复杂,但实现起来并不复杂

class CacheInterceptor: ExecutionInterceptor {

    let controller = CacheController()

    func intercept(
        chain: InterceptorChain<Data>
    ) {
        // On that case, the cache controller may need to finish 
        // the operation or not (depending on the policies).
        // So, we can easily tell the chain wether the operation 
        // should be finished or not.
        defer { chain.proceed() }
        guard let cachedResponse = 
            try? controller.cachedResponse(for: chain.endpoint) 
            else { return }
        chain.complete(
            with: cachedResponse.data, 
            finish: cachedResponse.finish
        )
    }

}

失败

基本上,此拦截器的目的是在Alamofire检索到错误时进行拦截。一个简单的例子可以是

class ErrorValidator: FailureInterceptor {

    func intercept(
        chain: InterceptorChain<Data>,
        error: Swift.Error
    ) {
        defer { chain.proceed() }
        guard case Error.some = error else { return }
        chain.complete(with: Error.another)
    }

}

成功

基本上,此拦截器的目的是在Alamofire检索到响应时进行拦截。让我给你举两个例子

我们知道,有时,API可能会检索到包含更多信息的自定义错误

class BodyValidator: SuccessInterceptor {

    func intercept(
        chain: InterceptorChain<Data>, 
        response: HTTPURLResponse, 
        data: Data
    ) {
        defer { chain.proceed() }

        guard let error = 
            try? chain.client.manager.jsonDecoder.decode(APIError.self, from: data) 
            else { return }
        chain.complete(
            with: Error.server(error)
        )
    }

}

也许没有自定义错误,但我们仍需要验证响应的状态码

class ResponseValidator: SuccessInterceptor {

    func intercept(
        chain: InterceptorChain<Data>, 
        response: HTTPURLResponse, 
        data: Data
    ) {
        defer { chain.proceed() }

        let error: Error

        switch response.statusCode {
            // You should match your errors here.
            case 200...299: return
            case 401, 403: error = .unauthorized
            default: error = .unknown
        }

        chain.complete(with: error)
    }

}

完成

拦截器的目的是在调用完成处理程序之前进行拦截。让我给你举两个例子

再次,最简单的一个,我们需要记录每次响应

class LoggerInterceptor: CompletionInterceptor {

    func intercept(
        chain: InterceptorChain<Data>, 
        response: Response<Data>
    ) {
        defer { chain.proceed() }
        guard let request = try? chain.request.convertible.asURLRequest(),
            let method = request.httpMethod,
            let url = request.url?.absoluteString
            else { return }

        switch response {
        case .success:
            Logger.shared.logDebug("✔✔✔ \(method) \(url)")
        case .failure(let error):
            Logger.shared.logDebug("✖✖✖ \(method) \(url)")
            Logger.shared.logError(error)
        }
    }

}

现在,更复杂的一个,我们需要在过期时更新身份验证。这里有两种选择

class AuthenticationValidator: CompletionInterceptor {

    func intercept(
        chain: InterceptorChain<Data>, 
        response: Response<Data>
    ) {
        guard let error = response.error, case Error.unauthorized = error else {
            chain.proceed()
            return
        }

        RefreshTokenManager.shared.refreshTokenIfNeeded { authenticated in
            guard authenticated else {
                chain.complete(with: Error.unableToAuthenticate)
                return
            }

            do {
                try chain.retry()
            } catch {
                // In almost every case, no error is thrown.
                // But, because we prefer the safest way, we use the do-catch.
                chain.complete(with: Error.unableToRetry)
            }
        }
    }

}

序列化

这是最后被调用的拦截器。它也是可选的。这取决于你是否需要序列化你的响应或者你只需要数据。

序列化过程建立在从请求中获取 Data 的过程之上。这就是为什么它总是在所有之前的 Interceptors 之后调用的原因。让我给你举一个例子。

我们之前提到了 CacheController,对吧?现在,如果响应被成功序列化,我们需要更新缓存。

class CacheInterceptor: SerializationInterceptor {

    let controller = CacheController()

    func intercept<T: DataResponseSerializerProtocol>(
        chain: InterceptorChain<T.SerializedObject>,
        response: Response<Data>,
        result: Result<T.SerializedObject, Swift.Error>,
        serializer: T
    ) {
        defer { chain.proceed() }
        guard let value = response.value, 
            (try? result.get()) != nil 
            else { return }
        controller.updateCacheIfNeeded(
            for: chain.endpoint, 
            value: value
        )
    }

}

RxSwift

你使用 RxSwift 吗?为此有一个扩展!让我给你演示如何使用它。

client.rx.execute(
    APIEndpoint.readAllUsers, 
    type: [User].self
).subscribe { event in
    // Do whatever you have to do with the response here.
}

好吧,这很棒。但是,如果我们只想这样调用怎么办呢?

usersClient.rx.readAll().subscribe { event in
    // Do whatever you have to do with the response here.
}

简单多了,对吧?如前所述,为了使你的项目尽可能简单和清晰,请遵循 示例项目 的架构。

沟通

  • 如果 你需要帮助,请提出一个问题。
  • 如果你 发现了一个错误,请提出一个问题。
  • 如果你 有一个功能请求,请提出一个问题。
  • 如果你 想要做出贡献,提交一个拉取请求。

作者

卢西亚诺·波利特,[email protected]

许可

Leash 可在 MIT 许可下获得。有关更多信息,请参阅 LICENSE 文件。