iOS Moya Web 服务:发出一个呼叫等待所有其他呼叫完成,然后阻止新呼叫

iOS Moya Web Services: make one call wait for all others to finish, then block new calls

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

每个 Web 服务调用都需要在其 headers 中进行身份验证。当用户更改密码时,令牌为 re-generated 并由更改密码 Web 服务调用返回。

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

我想避免这种情况,但我不确定最好的方法是什么,或者我的想法是否有误。

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

看来我应该这样使用 "Read-write lock":

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

我想分享一些关于 RxSwift 中内置机制的想法。我认为有一些方法可以实现所需的行为。

The code below is just a theory and hasn't been tested. Please, don't cmd+c cmd+v this code. Its purpose is to show the simplified version of potential solution.

假设我们有:

var activityIndicator = ActivityIndicator()
var relayToken = BehaviorRelay<String?>(value: nil)

其中 ActivityIndicatorthis struct,这有助于捕获多个 Observables.

的 activity

理论上,请求方法看起来像这样:

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, [=11=]) })
                .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 或其他机制的地方。

此函数等待单个有效令牌信号,并在收到适当的令牌时 - 将其转换为响应 ObservableactivityIndicator 也会跟踪该响应。需要此类跟踪才能知道所有 "read" 调用何时结束。

因此 - 每个请求 activity 都会被跟踪,并且只有在收到有效令牌时才会启动任何请求。

第二个重要的事情 - 仅在没有活动请求时更改令牌:

func update(token: String?) {
    _ = Observable
        .just(token)
        .flatMap({ (token: String?) -> Observable<String?> in
            return activityIndicator
                .asObservable()
                .filter({ [=12=] == false })
                .take(1)
                .map({ _ in token })
        })
        .bind(to: relayToken)
}

因此,无论何时您决定更改令牌 - 您都可以通过此功能应用它的更改。它观察所有请求的 activity,当它们全部完成时 - 将应用更改。

希望对您有所帮助,如有需要请提问。

编辑 1

"PATCH changePassword" 可能不是正常请求之一,它的 activity 可能不会被 activityIndicator.

跟踪
  1. 当您需要更改密码时设置relayToken = nil(从这一步开始,所有未来的正常请求都将等待正确的令牌)
  2. 等待已经开始的请求完成(activityIndicator 将再次提供帮助)
  3. 发送"PATCH changePassword"请求更改密码/令牌
  4. 写入新令牌relayToken = some
  5. 所有暂停的请求都会得到一个新的令牌并自动开始执行
  6. 需要时返回第 1 步

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

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

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

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