处理可观察序列 RxSwift 上的循环样式事件

Handling circular style events on observable sequence RxSwift

我想知道处理以下情况的最佳方法,我已经尝试了一种方法,但我遇到了一个事件以循环方式反复调用彼此的问题,因此导致计算器溢出

我有 4 个 observables 如下:-

    let agreeToPrivacyPolicyObservable = BehaviorRelay<Bool>(value: false)
    let agreeToTermsObservable = BehaviorRelay<Bool>(value: false)
    let agreeToMarketingEmailObservable = BehaviorRelay<Bool>(value: false)
    let agreeToAllOptionsObservable = BehaviorRelay<Bool>(value: false)

目标: 同步同意所有按钮与个别选项。即如果同意所有是 true/checked 然后强制检查其他选项,反之亦然。此外,如果检查了所有项目的先前状态并且其中任何一个未选中,则删除同意所有按钮上的复选标记。

下图形象化了我上面的目标。

我试过的:

Observable.combineLatest(
agreeToPrivacyPolicyObservable,
agreeToTermsObservable,
agreeToMarketingEmailObservable,
agreeToAllOptionsObservable
, resultSelector:{(termsChecked,privacyChecked,marketingChecked,agreeToAllChecked) in 
  switch (termsChecked,privacyChecked,marketingChecked,agreeToAllChecked) {
        case (true, true, true,true):
         //All boxes are checked nothing to change.
             break
        case (false,false,false,false):
          //All boxes are unchecked nothing to change.
           break
       case (true,true,true,false):
     // I omitted the `triggeredByAgreeToAll` flag implementation details for clarity
         if triggeredByAgreeToAll {
              updateIndividualObservables(checked: false)
            }else {
               agreeToAllOptionsObservable.accept(true)
           }
    case (false,false,false,true):
         if triggeredByAgreeToAll {
             updateIndividualObservables(checked: true)
           }else {
               agreeToAllOptionsObservable.accept(false)
          }
  default:
     if triggeredByAgreeToAll && agreeToAllChecked {
        updateIndividualObservables(checked: true)               
       }else if triggeredByAgreeToAll && agreeToAllChecked == false {
            updateIndividualObservables(checked: false)
      } else if (termsChecked == false || privacyChecked == false || marketingChecked == false ) {
       agreeToAllOptionsObservable.accept(false)
        }
     }
                    
  }

})
.observeOn(MainScheduler.instance)
.subscribe()
.disposed(by: rx.disposeBag)
// Helper function
  func updateIndividualObservables(checked: Bool) {
      agreeToPrivacyPolicyObservable.accept(checked)
      agreeToTermsObservable.accept(checked)
      agreeToMarketingEmailObservable.accept(checked)
     }

解释: 我的尝试给了我 Reentracy anomaly was detected error ,根据我的观察,这是由重复触发的事件引起的。这似乎发生在默认开关案例中(在我上面的解决方案中)。我认为这个解决方案不好,因为我必须检查哪个事件触发了函数执行。

是否有更好的方法或者是否可以将此解决方案重构为易于管理的东西?顺便说一句,请随意忽略我的实现,并建议其他更好的方法(如果有的话)。谢谢!

更新(工作解决方案)

我使用 @Rugmangathan 的想法成功实施了一个可行的解决方案(在已接受的答案中找到)。所以我把我的解决方案留在这里,以帮助将来遇到同样问题的任何人。 这是工作解决方案:-

import Foundation
import RxSwift
import RxRelay


/// This does all the magic of selecting checkboxes.
/// It is shared across any view which uses the license Agreement component.
class LicenseAgreemmentState {
    
      static let shared = LicenseAgreemmentState()
     let terms = BehaviorRelay<Bool>(value: false)
     let privacy = BehaviorRelay<Bool>(value: false)
     let marketing = BehaviorRelay<Bool>(value: false)
     let acceptAll = BehaviorRelay<Bool>(value: false)
    
    private let disposeBag = DisposeBag()
    
     func update(termsChecked: Bool? = nil, privacyChecked: Bool? = nil, marketingChecked: Bool? = nil, acceptAllChecked: Bool? = nil) {
        if let acceptAllChecked = acceptAllChecked {
            // User toggled acceptAll button so change everything to it's value.
            acceptAll.accept(acceptAllChecked)
            updateIndividualObservables(termsChecked: acceptAllChecked, privacyChecked: acceptAllChecked, marketingChecked: acceptAllChecked)
        } else {
            // If either of the individual item is missing change acceptAll to false
            if termsChecked == nil || privacyChecked == nil || marketingChecked == nil {
                acceptAll.accept(false)
            }
            updateIndividualObservables(termsChecked: termsChecked, privacyChecked: privacyChecked, marketingChecked: marketingChecked)
        }
        
       // Deal with the case user triggered select All from individual items and vice-versa.
        Observable.combineLatest(terms, privacy, marketing,resultSelector: {(termsChecked,privacyChecked, marketingChecked) in
            switch (termsChecked,privacyChecked, marketingChecked) {
            case (true, true, true):
                self.acceptAll.accept(true)
            case (false,false,false):
                self.acceptAll.accept(false)
            default:
                break
            }
        })
            .observeOn(MainScheduler.instance)
            .subscribe()
            .disposed(by: disposeBag)
    }
    
    // MARK: - Helpers
    private func updateIndividualObservables(termsChecked: Bool?,privacyChecked: Bool?, marketingChecked:Bool?) {
        if let termsChecked = termsChecked {
            terms.accept(termsChecked)
        }
        if let privacyChecked = privacyChecked {
            privacy.accept(privacyChecked)
        }
        if let marketingChecked = marketingChecked {
            marketing.accept(marketingChecked)
        }
    }
}

您的辅助函数 updateIndividualObservables(:) 会在您每次更新时触发一个事件,这反过来会触发您在上面实现的 combineLatest

我建议您保留一个 State 对象

struct TermsAndConditionState {
  var terms: Bool
  var privacy: Bool
  var marketing: Bool
  var acceptAll: Bool
}

updateIndividualObservables 方法中更改此状态并使用您各自的复选框实施此状态更改

func render(state: TermsAndConditionState) {
  if state.acceptAll {
    // TODO: update all checkboxes
  } else {
    // TODO: update individual checkboxes
  }
}

这是一个简单的状态机。状态机在 Rx 中使用 scan(_:accumulator:)scan(into:accumulator:) 运算符实现,如下所示:

struct Input {
    let agreeToPrivacyPolicy: Observable<Void>
    let agreeToTerms: Observable<Void>
    let agreeToMarketingEmail: Observable<Void>
    let agreeToAllOptions: Observable<Void>
}

struct Output {
    let agreeToPrivacyPolicy: Observable<Bool>
    let agreeToTerms: Observable<Bool>
    let agreeToMarketingEmail: Observable<Bool>
    let agreeToAllOptions: Observable<Bool>
}

func viewModel(input: Input) -> Output {
    enum Action {
        case togglePrivacyPolicy
        case toggleTerms
        case toggleMarketingEmail
        case toggleAllOptions
    }

    let action = Observable.merge(
        input.agreeToPrivacyPolicy.map { Action.togglePrivacyPolicy },
        input.agreeToTerms.map { Action.toggleTerms },
        input.agreeToMarketingEmail.map { Action.toggleMarketingEmail },
        input.agreeToAllOptions.map { Action.toggleAllOptions }
    )
    let state = action.scan(into: State()) { (current, action) in
        switch action {
        case .togglePrivacyPolicy:
            current.privacyPolicy = !current.privacyPolicy
        case .toggleTerms:
            current.terms = !current.terms
        case .toggleMarketingEmail:
            current.marketingEmail = !current.marketingEmail
        case .toggleAllOptions:
            if !current.allOptions {
                current.privacyPolicy = true
                current.terms = true
                current.marketingEmail = true
            }
        }
        current.allOptions = current.privacyPolicy && current.terms && current.marketingEmail
    }

    return Output(
        agreeToPrivacyPolicy: state.map { [=10=].privacyPolicy },
        agreeToTerms: state.map { [=10=].terms },
        agreeToMarketingEmail: state.map { [=10=].marketingEmail },
        agreeToAllOptions: state.map { [=10=].allOptions }
    )
}

struct State {
    var privacyPolicy: Bool = false
    var terms: Bool = false
    var marketingEmail: Bool = false
    var allOptions: Bool = false
}