处理可观察序列 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
}
我想知道处理以下情况的最佳方法,我已经尝试了一种方法,但我遇到了一个事件以循环方式反复调用彼此的问题,因此导致计算器溢出
我有 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
}