在我的应用程序中,我正在使用Moya和RxSwift进行Web服务调用。这些调用是异步的,可以通过用户交互触发,也可以在新数据可用时通过远程通知触发。
每个 Web 服务调用都需要在其标头中进行身份验证。当用户更改密码时,令牌将重新生成,并由更改密码 Web 服务调用返回。
现在,当用户更改密码时,可能会发出远程通知并导致另一个 Web 服务调用。根据服务器负载和系统处理不同线程的方式,理论上可能会发生调用是在另一个调用检索新令牌之前进行的,但在服务器已经使旧令牌无效之后。结果是 HTTP 401 未指定错误。
我想防止这种情况发生,但我不确定最好的方法是什么,或者我的概念中是否有一些错误的想法。
我找到了这个页面,其中讨论了锁,互斥锁和信号量:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html
似乎我应该使用"读写锁"如下:
- 更改密码调用是"编写器">
- 所有其他调用都是"读者"
- 当用户调用重新加载数据或由远程通知引起时,锁上的读取器计数将递增
- 当用户更改密码时,锁定上的写入器计数将递增,并阻止新读取器启动
- 更改密码调用等待所有其他"读取"调用完成 更改密码
- 调用更改密码,更新令牌,最后递减锁并将其释放
- 挂起的读取器现在可以继续运行并开始增加读取器计数并重新加载数据。
到目前为止,这是正确的吗?那么下一个大问题是:有没有更好的方法?在我开始更改所有 Web 服务调用之前:Moya 或 RxSwift 中是否有内置机制?
我想分享一些关于在 RxSwift中构建机制的想法。我认为有一些方法可以实现预期的行为。
下面的代码只是一个理论,尚未经过测试。请不要 cmd+c cmd+v 此代码。其目的是显示简化版本 的潜在解决方案。
假设我们有:
var activityIndicator = ActivityIndicator()
var relayToken = BehaviorRelay<String?>(value: nil)
其中ActivityIndicator
是这个结构,它有助于捕获多个Observables
的活动。
理论上,请求方法如下所示:
func request<D, P, R>(data: D, parameters: @escaping (D, String) -> P, response: @escaping (P) -> Observable<R>) -> Observable<R> {
return Observable
.just(data)
.flatMap({ (data: D) -> Observable<R> in
return relayToken
.asObservable()
.filterNil()
.take(1)
.map({ parameters(data, $0) })
.flatMap({ (parameters: P) -> Observable<R> in
return activityIndicator.trackActivity(response(parameters))
})
})
}
哪里:
data: D
- 初始请求参数
parameters: @escaping (D, String) -> P
- 将带有令牌的初始参数转换为完整请求参数的闭包。
response: @escaping (P) -> Observable<R>
- 将完整参数转换为正确的请求Observable
的闭包。这是一个使用莫亚或其他机制的地方。
此函数等待单个有效的令牌信号,当收到适当的令牌时 - 它将其转换为响应Observable
,该响应也由activityIndicator
跟踪。需要此类跟踪才能知道所有"读取"调用何时完成。
因此 - 跟踪每个请求活动,并且仅在收到有效令牌时启动任何请求。
第二件重要的事情 - 仅在没有活动请求时才更改令牌:
func update(token: String?) {
_ = Observable
.just(token)
.flatMap({ (token: String?) -> Observable<String?> in
return activityIndicator
.asObservable()
.filter({ $0 == false })
.take(1)
.map({ _ in token })
})
.bind(to: relayToken)
}
因此,每当您决定更改令牌时 - 您都可以通过此功能应用其更改。它观察所有请求的活动,当它们全部完成时 - 将应用更改。
希望它有帮助,如果需要,请提出问题。
编辑 1
"PATCH changePassword"可能不是正常请求之一,并且可能无法使用activityIndicator
跟踪其活动。
- 设置
relayToken = nil
何时需要更改密码(自此步骤以来,所有将来的正常请求都将等待正确的令牌) - 等待已启动的请求完成(
activityIndicator
将再次提供帮助) - 发送"PATCH更改密码"请求以更改密码/令牌
- 写入新的令牌
relayToken = some
- 所有暂停的请求都将获得一个新令牌并自动开始执行
- 需要时返回步骤 1
因此,服务器只会在所有已启动的请求完成后使令牌失效。
我的解决方案阻止以下位置的所有新请求:
relayToken.asObservable().filterNil().take(1)
这意味着,虽然令牌nil
- 等待。不nil
时 - 仅执行一次。