Ваш обновленный код не будет работать. Даже если вы не отправите завершенное событие из функции, оно все равно будет отправлено из takeUntil
, и поэтому этот оператор не будет выдавать больше значений.
Тем не менее, эта идея может быть реализована. Поскольку ты сказал, что хочешь учиться, я буду писать весь мой мыслительный процесс, пока буду писать.
Сначала давайте наметим наши входы и выходы. Для входных данных у нас есть две Observables, и длительность, и всякий раз, когда мы имеем дело с длительностью, нам нужен планировщик. Для выходов у нас есть только одна наблюдаемая.
Итак, объявление функции выглядит так:
func filterDuration(first: Observable<Void>, second: Observable<Void>, duration: TimeInterval, scheduler: SchedulerType) -> Observable<Void> {
// code goes here.
}
Мы будем сравнивать время срабатывания двух наблюдаемых, поэтому мы должны отследить это:
let firstTime = first.map { scheduler.now }
let secondTime = second.map { scheduler.now }
И поскольку мы сравниваем их, мы должны как-то их объединить. Мы могли бы использовать merge
, combineLatest
или zip
...
combineLatest
сработает всякий раз, когда сработает какая-либо наблюдаемая, и даст нам последние значения из обеих наблюдаемых.
zip
будет сопряжено, 1 на 1, события из обеих наблюдаемых. Это звучит интригующе, но может сломаться, если один из наблюдаемых срабатывает чаще, чем другой, поэтому он кажется немного ломким.
merge
сработает при срабатывании любого из них, поэтому нам нужно отследить, какой из них выстрелил каким-то образом (возможно, с помощью перечисления).
Давайте использовать combineLatest
:
Observable.combineLatest(firstTime, secondTime)
Это даст нам Observable<(RxTime, RxTime)>
, так что теперь мы можем сопоставить наши два раза и сравнить их. Цель здесь - вернуть Bool
, которое истинно, если второй раз больше первого раза более чем на duration
.
.map { arg -> Bool in
let (first, second) = arg
let tickDuration = second.timeIntervalSince(first)
return duration <= tickDuration
}
Теперь вышеприведенное будет срабатывать каждый раз, когда срабатывает любой из двух входов, но мы заботимся только о событиях, которые излучают true
. Это требует filter
.
.filter { $0 } // since the wrapped value is a Bool, this will accomplish our goal.
Теперь наша цепь испускает Bools, что всегда будет правдой, но мы хотим пустоту. Как насчет того, чтобы просто выбросить значение?
.map { _ in }
Собрав все вместе, мы получим:
func filterDuration(first: Observable<Void>, second: Observable<Void>, duration: TimeInterval, scheduler: SchedulerType) -> Observable<Void> {
let firstTime = first.map { scheduler.now }
let secondTime = second.map { scheduler.now }
return Observable.combineLatest(firstTime, secondTime)
.map { arg -> Bool in
let (first, second) = arg
let tickDuration = second.timeIntervalSince(first)
return duration <= tickDuration
}
.filter { $0 }
.map { _ in }
}
Вышеизложенное изолирует нашу логику и, что не случайно, легко проверить с помощью RxTests. Теперь мы можем заключить его в UIView (это было бы трудно проверить).
func observeLongPress(with minimumPressDuration: Double = 1) -> Observable<Void> {
let touchesBeganEvent = rx.methodInvoked(#selector(touchesBegan)).map { _ in }
let touchesEndedEvents = [#selector(touchesEnded), #selector(touchesCancelled)]
.map(rx.methodInvoked)
let touchesEndedEvent = Observable.merge(touchesEndedEvents).map { _ in }
return filterDuration(first: touchesBeganEvent, second: touchesEndedEvent, duration: minimumPressDuration, scheduler: MainScheduler.instance)
}
Вот, пожалуйста. Один пользовательский оператор.