问题描述
我想知道处理以下情况的最佳方法,我尝试了将要描述的方法,但是遇到了以循环方式反复调用彼此的事件,因此会导致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)
目标: 同步同意所有带有单独选项的按钮。即,如果同意所有人都正确/已检查,则也要强制检查其他选项,反之亦然。此外,如果所有项目的先前状态均已选中,并且其中任何一个都未选中,则删除“同意所有”按钮上的复选标记。
下面的图片使我的目标形象化。
我尝试过的事情:
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
}