1

在我的应用程序中,我使用 Moya 和 RxSwift 进行 Web 服务调用。这些调用是异步的,可以通过用户交互以及新数据可用时的远程通知来触发。

每个 Web 服务调用都需要在其标头中进行身份验证。当用户更改密码时,更改密码 Web 服务调用会重新生成并返回令牌。

现在,当用户更改密码时,可能会出现远程通知并导致另一个 Web 服务调用。根据服务器负载以及系统如何处理不同的线程,理论上可能会发生调用是在另一个调用检索到新令牌之前但在服务器已经使旧令牌无效之后进行的。结果是 HTTP 401 未经授权的错误。

我想防止这种情况发生,但我不确定最好的方法是什么,或者我的概念是否有错误的想法。

我发现这个页面讨论了锁、互斥体和信号量:https ://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html

看来我可能应该像这样使用“读写锁”:

  • 更改密码调用是“作家”
  • 所有其他电话都是“读者”
  • 当调用重新加载数据时,无论是由用户还是由远程通知引起的,读卡器计数都会在锁上增加
  • 当用户更改密码时,锁上的写入器计数会增加,并且阻止新的读取器启动
  • 更改密码调用等待所有其他“读取”调用完成
  • 更改密码调用更改密码,更新令牌,最后减少锁并释放它
  • 暂停的阅读器现在可以继续运行并开始增加阅读器计数并重新加载数据。

到目前为止这是正确的吗?那么下一个大问题是:有没有更好的方法?在我开始更改所有 Web 服务调用之前:在 Moya 或 RxSwift 中是否有内置机制?

4

1 回答 1

3

我想分享一些关于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这是一个使用Moya或其他机制的地方。

该函数等待一个有效的令牌信号,当接收到一个正确的令牌时,它会将其转换为响应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

  1. 设置relayToken = nil何时需要更改密码(自此步骤以来,所有未来的正常请求都将等待正确的令牌)
  2. 等待已经开始的请求完成(activityIndicator将再次帮助)
  3. 发送“PATCH changePassword”请求更改密码/令牌
  4. 写入新令牌relayToken = some
  5. 所有暂停的请求都将获得一个新的令牌并自动开始执行
  6. 需要时返回步骤 1

因此,只有在所有已经启动的请求都完成后,服务器才会使令牌失效。

我的解决方案阻止了所有新请求:

relayToken.asObservable().filterNil().take(1)

这意味着,虽然令牌是nil- 等待。如果没有nil- 只进行一次。

于 2018-01-12T04:22:10.827 回答