在可观察序列RxSwift上处理圆形样式事件

问题描述

我想知道处理以下情况的最佳方法,我尝试了将要描述的方法,但是遇到了以循环方式反复调用彼此的事件,因此会导致stackoverflow?

我有4个可观察值,如下所示:-

    let agreetoPrivacyPolicyObservable = BehaviorRelay<Bool>(value: false)
    let agreetoTermsObservable = BehaviorRelay<Bool>(value: false)
    let agreetoMarketingEmailObservable = BehaviorRelay<Bool>(value: false)
    let agreetoAllOptionsObservable = BehaviorRelay<Bool>(value: false)

目标: 同步同意所有带有单独选项的按钮。即,如果同意所有人都正确/已检查,则也要强制检查其他选项,反之亦然。此外,如果所有项目的先前状态均已选中,并且其中任何一个都未选中,则删除“同意所有”按钮上的复选标记

下面的图片使我的目标形象化。

Observables

我尝试过的事情:

Observable.combineLatest(
agreetoPrivacyPolicyObservable,agreetoTermsObservable,agreetoMarketingEmailObservable,agreetoAllOptionsObservable,resultSelector:{(termsChecked,privacyChecked,marketingChecked,agreetoAllChecked) in 
  switch (termsChecked,agreetoAllChecked) {
        case (true,true,true):
         //All Boxes are checked nothing to change.
             break
        case (false,false,false):
          //All Boxes are unchecked nothing to change.
           break
       case (true,false):
     // I omitted the `triggeredByAgreetoAll` flag implementation details for clarity
         if triggeredByAgreetoAll {
              updateIndividualObservables(checked: false)
            }else {
               agreetoAllOptionsObservable.accept(true)
           }
    case (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)
     }

说明: 我的尝试给了我检测到再入异常的错误,根据我的观察,这是由重复触发的事件引起的。这似乎发生在认开关的情况下(在我上面的解决方案中)。我认为此解决方案不好,因为我必须检查哪个事件触发了函数执行。

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

更新(工作解决方案)

我通过使用 @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,marketingChecked) in
            switch (termsChecked,marketingChecked) {
            case (true,true):
                self.acceptAll.accept(true)
            case (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
  }
}
,

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

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 { $0.privacyPolicy },agreeToTerms: state.map { $0.terms },agreeToMarketingEmail: state.map { $0.marketingEmail },agreeToAllOptions: state.map { $0.allOptions }
    )
}

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