【问题标题】:iOS Moya Web Services: make one call wait for all others to finish, then block new callsiOS Moya Web Services:拨打一个电话等待所有其他电话完成,然后阻止新电话
【发布时间】:2018-01-12 02:34:56
【问题描述】:

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

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

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

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

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

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

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

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

【问题讨论】:

    标签: ios multithreading web-services rx-swift moya


    【解决方案1】:

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

    下面的代码只是一个理论,还没有经过测试。请不要 cmd+c cmd+v 这段代码。其目的是展示简化版 的潜在解决方案。

    假设我们有:

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

    其中ActivityIndicatorthis struct,这有助于捕获多个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) -&gt; P - 将带有令牌的初始参数转换为完整请求参数的闭包。

    response: @escaping (P) -&gt; Observable&lt;R&gt; - 将完整参数转换为正确请求的闭包Observable。这是一个使用 Moya 或其他一些机制的地方。

    此函数等待一个有效的令牌信号,当接收到正确的令牌时,它会将其转换为响应ObservableactivityIndicator 也会跟踪该响应。此类跟踪需要知道所有“读取”调用何时完成。

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

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

    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 - 只进行一次。

    【讨论】:

    • 感谢您的帖子。只有一件事我要么不完全理解,要么在您的解决方案中不起作用。所有调用,“GET loadData”和“PATCH changePassword”都是异步的。一旦我调用更改密码,服务器可能已经使令牌无效,然后创建并返回一个新令牌。与此同时,可以进行另一个“GET loadData”调用,但我需要阻止该调用,直到收到新令牌。当“PATCH changePassword”运行时,您的解决方案在哪里阻止新的“GET loadData”调用?在此先感谢,扬
    • 谢谢,现在我明白了。我会尝试实施你的解决方案,然后给你我的反馈。 :)
    • 我认为这可以完成工作。我实现了它,从第一次测试开始,它似乎可以正常工作。非常感谢。 :)
    • @xxtesaxx 很高兴听到它有帮助。不要忘记在发送“PATCH changePassword”请求时捕获错误,因为如果失败 - 整个请求链将被阻止
    • 知道了。 :) 我捕获了所有内容,如果出现错误,我会重置令牌,以便其他调用可以运行,我可以向用户显示错误
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多