Как вытащить одно событие из наблюдаемого, когда другое излучает - PullRequest
0 голосов
/ 12 ноября 2018

Я не могу понять, как извлечь одно событие из наблюдаемого, когда другое срабатывает (по крайней мере, я думаю, что это суть проблемы, но я так растерялся, что могу ошибаться).

У меня есть ViewModel, которому передается массив объектов при инициализации. Соответствующий ViewController показывает один из этих объектов одновременно для пользователя, чтобы принять или отклонить его (две кнопки), а также возможность применить ответ ко всем оставшимся объектам (флажок). Побочным эффектом принятия объекта (и, возможно, всех остальных) является вставка базы данных. Когда больше нет объектов, ViewController будет закрыт.

Как я могу моделировать это реактивно (используя RxSwift / Cocoa)?

Я также хотел бы показать пользователю, сколько осталось объектов, но это кажется дополнительной сложностью.

1 Ответ

0 голосов
/ 13 ноября 2018

Это пример реализации для описанного поведения.

Помните, что 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. takeUntil сделает нашу наблюдаемую завершенной, когда applyToAll испустит. Так что мы не получаем новый элемент, когда установлен флажок applyToAll.
  5. repeatElement будет бесконечно повторять элемент. Поэтому, когда applyToAll истинно, мы будем повторять решение до бесконечности. Поскольку мы сжали результат от flatMap до objects, он повторит решение о количестве оставшихся объектов в objects.

Чтобы построить источник, наблюдаемый для модели представления, предполагая, что вы используете два UIButton s и 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% правильно.

...