【问题标题】:How to pull one event from an observable when another emits当另一个事件发出时如何从一个可观察的事件中提取一个事件
【发布时间】:2018-11-12 20:19:11
【问题描述】:

当另一个事件触发时,我不知道如何从 observable 中提取一个事件(至少,我认为这是问题的本质,但我很困惑,我可能错了)。

我有一个 ViewModel,它在初始化时传递了一个对象数组。相应的 ViewController 一次显示其中一个对象供用户接受或拒绝(两个按钮),以及将响应应用于所有剩余对象的选项(一个复选框)。接受对象(可能还有所有剩余的对象)的副作用是数据库插入。当没有更多对象时,ViewController 将被关闭。

我如何响应式地建模(使用 RxSwift/Cocoa)?

我还希望能够向用户显示剩余的对象数量,但这似乎是一种额外的复杂性。

【问题讨论】:

  • 到目前为止您尝试过什么?有什么代码可以分享吗?

标签: swift rx-swift reactivex


【解决方案1】:

这是所描述行为的示例实现。

请记住,尽管 stackoverflow 并不是这样工作的。您应该首先展示您编写的代码以尝试解决您的问题。

// 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 { $0.applyToAll }.filter { $0 == true }.map { _ in }
        let acceptOrDecline = input.acceptOrDecline.map { $0.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
        )
    }
}
  1. 在这里,我创建了一个 obversable,它将依次发出源数组中的每个元素。
  2. zip 组合了来自两个可观察对象的元素。 zip 的好处是它会等待来自每个来源的不同元素。订阅 zip 的结果时,input.acceptOrDecline 发出后会发出一个新元素。因此,我们会在每个决定后收到一个新对象。
  3. startWith() 将强制进行第一次发射,以便我们收到我们希望用户做出决定的第一个对象。
  4. 当applyToAll 发出时,takeUntil 将使我们的 observable 完成。这样我们就不会在选中 applyToAll 复选框时收到新元素。
  5. repeatElement 将无限期地重复一个元素。因此,当applyToAll 为真时,我们将无限期地重复该决定。因为我们将flatMap的结果压缩到objects,所以会重复判断objects中的remaning对象个数。

要为视图模型构建源 observable,假设您使用两个 UIButtons 和一个 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: $0, applyToAll: $1) }
)

注意,这是一个可以编译但我没有测试的建议实现。您会在此处找到实现所需行为的线索,但我不能保证这是 100% 正确的。

【讨论】:

    猜你喜欢
    • 2019-05-15
    • 2018-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多