Coverage Swift Package Manager compatible CocoaPods compatible Carthage compatible Gitter chat

索引

简介

想象一下这样的世界:网络请求听从你的指挥,每次调用都像一条训练有素的宠物,被结实的Leash牵引着。欢迎来到一个从重复的APIManagerAlamofire+ProjectName中解脱出来,如同在公园里悠闲漫步的地方。有了Leash,你不仅仅是构建一个网络层,你还在精心编排一场拦截器的交响乐,它们巧妙地以精确和优雅的方式引导你的请求。

渴望了解更多细节吗?拦截器部分是您掌握它的门户。 除了命令和控制之外,Leash还将编码解码和无缝身份验证的技巧带到您的指尖,同时与熟悉的 Alamofire 的旋律相协调。 通过Leash拥抱网络复杂性的艺术 - 在这里,您代码的潜力是无限的。

要求

安装

Swift Package Manager

要使用 Swift Package ManagerLeash 集成到您的项目中,请在您的 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: "YourTargetName",
            dependencies: [
                "Leash",
                "LeashInterceptors",
                "RxLeash"
            ]
        )
    ]
)

CocoaPods

要使用 CocoaPodsLeash 集成到您的项目中,请在您的 Podfile 中指定它

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

Carthage

要使用 CarthageLeash 集成到您的项目中,请在您的 Cartfile 中指定它

github "LucianoPolit/Leash" ~> 3.2

用法

设置

步骤 1:配置管理器

首先设置一个 Manager。您可以详细配置它,或者使用快捷方法。请参阅所有可配置的选项。配置示例

详细设置

let manager = Manager.Builder()
    .scheme("http") // Define the scheme.
    .host("localhost") // Specify the host.
    .port(8080) // Set the port.
    .path("api") // Define the base path.
    .build() // Build the manager.

或者,为了快速设置

let manager = Manager.Builder()
    .url("https://:8080/api") // Set the entire URL in one go.
    .build() // Build the manager.

步骤 2:创建客户端

接下来,初始化一个 Client 来处理您的请求

let client = Client(
    manager: manager
)

步骤 3:执行请求

设置好一个 Endpoint 之后,您就可以执行请求了。例如

client.execute(
    APIEndpoint.readAllUsers
) { (response: Response<[User]>) in
    // Handle the response here.
}

简化调用

更喜欢更简洁的方法?您可以像这样简化调用

usersClient.readAll { response in
    // Handle the response here.
}

这种精简的方法提高了可读性和效率。为了获得最佳实践和清晰的架构,请参考示例项目

编码

理解参数配置

Leash 提供了多种方式来配置不同类型请求的参数。 您可以使用各种编码,具体取决于您的端点是否需要查询参数或主体参数

示例实现

以下是如何实现每种类型

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

extension APIEndpoint: Endpoint {

    var path: String {
        "/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 let .first(request): return request // This is `QueryEncodable`.
        case let .second(request): return request // This is `[String: CustomStringConvertible]`.
        case let .third(request): return request // This is `Encodable`.
        case let .fourth(request): return request // This is `[String: Any]`.
        }
    }
    
}

使用的编码类

Leash 使用不同的编码类

自定义编码

要自定义参数编码,请覆盖 Client.urlRequest(for:) 方法。

如果您想以不同的方式编码参数,则必须覆盖 Client.urlRequest(for:) 方法。

解码

指定响应类型

在使用 Leash 执行请求的过程中,必须定义预期的响应类型,该类型必须符合 Decodable 协议。 此规范确保在收到成功响应后,Leash 会有效地将数据解码为您指定的类型。 这是一个简单的例子来说明这个过程

client.execute(
    APIEndpoint.readAllUsers
) { (response: Response<[User]>) in
    // On success, `response.value` will be of type `[User]`.
}

使用 JSONDecoder 序列化

Leash 使用 JSONDecoder 进行响应序列化。 要自定义此过程或使用其他序列化程序,请实现您自己的 响应序列化程序,利用 Alamofire 中的 DataResponseSerializerProtocol

示例:JSON 响应序列化器

extension DataRequest {

    @discardableResult
    func responseJSON(
        client: Client,
        endpoint: Endpoint,
        completion: @escaping (Response<Any>) -> Void
    ) -> Self {
        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
    // On success, `response.value` will be of type `Any`.
}

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

认证器

您是否需要验证您的请求? 使用 Leash 非常简单。 这是方法

class APIAuthenticator {

    var accessToken: String?

}

extension APIAuthenticator: Authenticator {

    static var header = "Authorization"

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

}

只需像这样注册您的身份验证器

let authenticator = APIAuthenticator()
let manager = Manager.Builder()
    { ... } // Include other necessary configurations here.
    .authenticator(authenticator) // Register the authenticator.
    .build() // Build the manager.

瞧! 您的请求现在已通过身份验证。 担心令牌过期? 查看 此处 的解决方案!

拦截器

解锁框架的核心力量

InterceptorsLeash 的强大功能,能够在请求生命周期的各个阶段执行操作。 它们分为五种类型,用于精确干预

生命周期集成

每个请求至少经过三种拦截器类型:ExecutionFailureSuccessCompletionSerialization 类型根据您是否正在序列化响应来使用。

模块化和异步执行

Manager 可以容纳一个 Interceptors 数组,它们以异步方式按添加的顺序执行。 这种排队可确保有序的顺序处理流程。 如果拦截器请求结束操作,则不会调用同一类型的后续拦截器,从而提供有效的错误处理和流程控制。

独立但有凝聚力

每个拦截器都独立运行,确保没有可能使您的项目结构复杂化的相互依赖性。 这种模块化设计允许轻松删除或添加拦截器,而不会影响其他组件。 此外,它们在不同项目中的可重用性增加了它们的通用性。

集成拦截器

要将拦截器添加到您的 Manager,请按照此模式

let manager = Manager.Builder()
    { ... } // Include other necessary configurations here.
    .add(
        interceptor: CustomInterceptor() // Insert your custom interceptor.
    )
    .build()

执行

增强请求准备

Execution Interceptor 允许进行请求前调整和监控。 两个关键实现证明了它的多功能性

  1. LoggerInterceptor:记录每个请求的详细信息,以便进行有效的跟踪和调试。
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)")
    }
    
}
  1. CacheInterceptor:确定是使用缓存的响应还是继续网络请求。
class CacheInterceptor: ExecutionInterceptor {

    let controller = CacheController()

    func intercept(
        chain: InterceptorChain<Data>
    ) {
        // In this scenario, the cache controller decides whether to complete
        // the operation based on predefined policies.
        // This allows us to instruct the chain to either finish or continue the operation.
        defer { chain.proceed() }

        guard let cachedResponse = try? controller.cachedResponse(
            for: chain.endpoint
        )
        else { return }

        chain.complete(
            with: cachedResponse.data, 
            finish: cachedResponse.finish
        )
    }

}

失败

优雅地处理请求错误

Alamofire 遇到错误时,Failure Interceptor 会介入。 考虑这个例子

ErrorValidator:一个简单但必不可少的拦截器,用于检查特定的错误条件并相应地修改或处理它们。

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 成功检索响应时,Success Interceptor 至关重要,它提供了执行其他验证或处理的机会。 以下是两个说明性示例

  1. BodyValidator:此拦截器解码响应数据以检查特定于 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)
        )
    }

}
  1. ResponseValidator:此拦截器侧重于验证响应的 HTTP 状态代码。 它根据状态代码对响应进行分类,为特定范围或条件分配适当的错误。
class ResponseValidator: SuccessInterceptor {

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

        let error: Error

        switch response.statusCode {
            case 200 ... 299: return
            case 401, 403: error = .unauthorized
            default: error = .unknown
        }

        chain.complete(
            with: error
        )
    }

}

完成

完善请求的最后阶段

Completion Interceptor 在提供完成处理程序之前执行。 这是两个例子

  1. LoggerInterceptor:记录每个响应的结果,区分成功和失败。
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)
        }
    }

}
  1. AuthenticationValidator:当出现身份验证问题时,这个更复杂的拦截器会介入。 它根据需要刷新令牌,确保持续的身份验证访问。
class AuthenticationValidator: CompletionInterceptor {

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

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

            do {
                try chain.retry()
            } catch {
                // Typically, retrying does not throw an error.
                // However, for maximum safety and to handle unexpected scenarios,
                // we wrap it in a do-catch block.
                chain.complete(
                    with: Error.unableToRetry
                )
            }
        }
    }

}

注意:您还可以使用 Alamofire 提供的 AdapterRetrier

序列化

序列化拦截器是请求生命周期中的最后也是可选的阶段,具体取决于您是否正在序列化您的响应或只是处理 Data

例如,在继续使用 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 的人来说,Leash 已经为您提供了一个专门的扩展。 以下是如何使用 RxSwift 简化您的网络调用

client.rx.execute(
    APIEndpoint.readAllUsers, 
    type: [User].self
).subscribe { event in
    // Handle the response event here.
}

寻求更简洁的方法? 您可以进一步简化它

usersClient.rx.readAll().subscribe { event in
    // Handle the response event here.
}

这种方法与保持您的项目简单和简洁的精神完全一致。 为了深入了解和最佳实践,请探索 示例项目 中概述的结构。

沟通

作者

Luciano Polit, lucianopolit@gmail.com

许可

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