Leash
索引
简介
您是否使用Alamofire?您是否习惯在每个新项目中创建一些名为 APIManager
或 Alamofire+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]`.
}
}
}
使用三个不同的类来编码这些参数
- URLEncoding:用于编码
QueryEncodable
和[String: CustomStringConvertible]
。 - JSONEncoding:用于编码
[String: Any]
。 - JSONEncoder:用于编码
Encodable
。
如果您想以不同的方式编码参数,您需要重写方法 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 文件。