如何在另一个事件发出时从一个可观察对象中提取一个事件
How to pull one event from an observable when another emits
我不知道如何在另一个事件触发时从一个可观察事件中提取一个事件(至少,我认为这是问题的本质,但我很困惑,我可能是错的)。
我有一个 ViewModel,它在初始化时传递了一个对象数组。相应的 ViewController 一次显示其中一个对象供用户接受或拒绝(两个按钮),以及将响应应用于所有剩余对象的选项(一个复选框)。接受对象(可能还有所有剩余的对象)的副作用是数据库插入。当没有更多对象时,ViewController 将被关闭。
我如何对此进行反应式建模(使用 RxSwift/Cocoa)?
我还希望能够向用户显示剩余的对象数量,但这似乎更加复杂。
这是所描述行为的示例实现。
请记住,Whosebug 并不是这样工作的。您应该首先展示您编写的代码以尝试解决您的问题。
// This encodes a database action. You can subscribe to view model's `dbAction` to perform your desired side effect.
enum DBAction<T> {
case insert(object: T)
case delete(object: T)
}
class SomeViewModel<Object> {
struct Output {
let currentObject: Observable<Object>
let remainingObjectCount: Observable<Int>
let dbAction: Observable<DBAction<Object>>
}
struct Input {
let acceptOrDecline: Observable<(keepOrDrop: Bool, applyToAll: Bool)>
}
let totalObjectCount: Int
let objects: Observable<Object>
init(objects: [Object]) {
self.totalObjectCount = objects.count
self.objects = .from(objects) // 1
}
func transform(input: Input) -> Output {
let applyToAll: Observable<Void> = input.acceptOrDecline.map { [=10=].applyToAll }.filter { [=10=] == true }.map { _ in }
let acceptOrDecline = input.acceptOrDecline.map { [=10=].keepOrDrop }
let currentObject = Observable.zip( // 2
objects,
acceptOrDecline.map { _ in }.startWith() // 3
) { object, _ in object }
.takeUntil(applyToAll) // 4
.share()
// 5
let actionForCurrent = input.acceptOrDecline.flatMap { tuple in
tuple.applyToAll ? Observable.repeatElement(tuple.keepOrDrop, scheduler: MainScheduler.instance) : .just(tuple.keepOrDrop)
}
let dbAction = Observable.zip(
objects,
actionForCurrent
) { (object: Object, shouldKeep: Bool) -> DBAction<Object> in
if shouldKeep {
return DBAction.insert(object: object)
} else {
return DBAction.delete(object: object)
}
}
let remainingObjectCount = currentObject.scan(totalObjectCount) { acc, _ in
acc - 1
}.concat(.just(0))
return Output(
currentObject: currentObject,
remainingObjectCount: remainingObjectCount,
dbAction: dbAction
)
}
}
- 这里我创建了一个 obversable,它将依次发射源数组中的每个元素。
- zip 组合来自两个可观察值的元素。 zip 的好处是它将等待来自每个源的不同元素。订阅 zip 的结果时,在
input.acceptOrDecline
发出后会发出一个新元素。因此,我们将在每次决定后收到一个新对象。
startWith()
将强制执行第一次发射,以便我们收到第一个我们希望用户做出决定的对象。
takeUntil
将使我们的 observable 在 applyToAll
发出时完成。这样我们就不会在选中 applyToAll
复选框时收到新元素。
repeatElement
将无限重复一个元素。因此,当 applyToAll
为真时,我们将无限期地重复该决定。因为我们把flatMap
的结果zip到objects
,所以会重复objects
. 中remaning object的个数判断
要为视图模型构建源可观察对象,假设您使用两个 UIButton
和一个 UISwitch
let acceptButton: UIButton
let dropButton: UIButton
let applyToAll: UISwitch
let accept = acceptButton.rx.tap.map { true }
let drop = dropButton.rx.tap.map { false }
let input = Input(
acceptOrDecline: Observable.combineLatest(
Observable.merge(accept, drop),
applyToAll.rx.value
) { (keepOrDrop: [=11=], applyToAll: ) }
)
注意这是一个建议的编译实现,但我没有测试。您会在这里找到实现您想要的行为的线索,但我不能保证这是 100% 正确的。
我不知道如何在另一个事件触发时从一个可观察事件中提取一个事件(至少,我认为这是问题的本质,但我很困惑,我可能是错的)。
我有一个 ViewModel,它在初始化时传递了一个对象数组。相应的 ViewController 一次显示其中一个对象供用户接受或拒绝(两个按钮),以及将响应应用于所有剩余对象的选项(一个复选框)。接受对象(可能还有所有剩余的对象)的副作用是数据库插入。当没有更多对象时,ViewController 将被关闭。
我如何对此进行反应式建模(使用 RxSwift/Cocoa)?
我还希望能够向用户显示剩余的对象数量,但这似乎更加复杂。
这是所描述行为的示例实现。
请记住,Whosebug 并不是这样工作的。您应该首先展示您编写的代码以尝试解决您的问题。
// This encodes a database action. You can subscribe to view model's `dbAction` to perform your desired side effect.
enum DBAction<T> {
case insert(object: T)
case delete(object: T)
}
class SomeViewModel<Object> {
struct Output {
let currentObject: Observable<Object>
let remainingObjectCount: Observable<Int>
let dbAction: Observable<DBAction<Object>>
}
struct Input {
let acceptOrDecline: Observable<(keepOrDrop: Bool, applyToAll: Bool)>
}
let totalObjectCount: Int
let objects: Observable<Object>
init(objects: [Object]) {
self.totalObjectCount = objects.count
self.objects = .from(objects) // 1
}
func transform(input: Input) -> Output {
let applyToAll: Observable<Void> = input.acceptOrDecline.map { [=10=].applyToAll }.filter { [=10=] == true }.map { _ in }
let acceptOrDecline = input.acceptOrDecline.map { [=10=].keepOrDrop }
let currentObject = Observable.zip( // 2
objects,
acceptOrDecline.map { _ in }.startWith() // 3
) { object, _ in object }
.takeUntil(applyToAll) // 4
.share()
// 5
let actionForCurrent = input.acceptOrDecline.flatMap { tuple in
tuple.applyToAll ? Observable.repeatElement(tuple.keepOrDrop, scheduler: MainScheduler.instance) : .just(tuple.keepOrDrop)
}
let dbAction = Observable.zip(
objects,
actionForCurrent
) { (object: Object, shouldKeep: Bool) -> DBAction<Object> in
if shouldKeep {
return DBAction.insert(object: object)
} else {
return DBAction.delete(object: object)
}
}
let remainingObjectCount = currentObject.scan(totalObjectCount) { acc, _ in
acc - 1
}.concat(.just(0))
return Output(
currentObject: currentObject,
remainingObjectCount: remainingObjectCount,
dbAction: dbAction
)
}
}
- 这里我创建了一个 obversable,它将依次发射源数组中的每个元素。
- zip 组合来自两个可观察值的元素。 zip 的好处是它将等待来自每个源的不同元素。订阅 zip 的结果时,在
input.acceptOrDecline
发出后会发出一个新元素。因此,我们将在每次决定后收到一个新对象。 startWith()
将强制执行第一次发射,以便我们收到第一个我们希望用户做出决定的对象。takeUntil
将使我们的 observable 在applyToAll
发出时完成。这样我们就不会在选中applyToAll
复选框时收到新元素。repeatElement
将无限重复一个元素。因此,当applyToAll
为真时,我们将无限期地重复该决定。因为我们把flatMap
的结果zip到objects
,所以会重复objects
. 中remaning object的个数判断
要为视图模型构建源可观察对象,假设您使用两个 UIButton
和一个 UISwitch
let acceptButton: UIButton
let dropButton: UIButton
let applyToAll: UISwitch
let accept = acceptButton.rx.tap.map { true }
let drop = dropButton.rx.tap.map { false }
let input = Input(
acceptOrDecline: Observable.combineLatest(
Observable.merge(accept, drop),
applyToAll.rx.value
) { (keepOrDrop: [=11=], applyToAll: ) }
)
注意这是一个建议的编译实现,但我没有测试。您会在这里找到实现您想要的行为的线索,但我不能保证这是 100% 正确的。