Комбинирование на основе данных
В предыдущем эпизоде мы успешно смоделировали поток значений и добавили один простой оператор (delay
) к каждому значению.
В этой статье мы рассмотрим еще несколько операторов, сделаем их Codeable
и, наконец, переведем их в издатель Combine во время выполнения.
Типы операторов
Прежде чем мы начнем моделировать операторы, нам нужно понять, сколько типов операторов существует.
Веб-сайт ReactiveX делит их примерно на 10 типов: создание, преобразование, фильтрация, комбинирование, обработка ошибок, служебная, условная, математическая / агрегированная, противодавление, подключаемые-наблюдаемые и операторы для преобразования наблюдаемых. Если вам интересно, у ReactiveX есть хорошее объяснение для каждого типа и оператора.
Примечание. Если вы не знакомы с RxSwift, Observable в RxSwift эквивалентно Publisher в Combine.
В предыдущей части мы упоминали оператор delay
, который относится к типу utility
. Сегодня мы сосредоточимся на сохранении двух операторов типа filtering
.
Оператор фильтрации
Этот тип оператора удаляет все или некоторые (или никакие) элементы потока из отправки вниз по потоку в зависимости от заданного условия.
dropFirst
dropFirst
останавливает отправку верхних n элементов. Учитывая простоту, мы можем добавить его в наше Operator
перечисление.
enum Operator { case delay(seconds: Double) case dropFirst(count: Int) }
Мы также можем легко преобразовать этот случай перечисления в Publisher.
extension Operator {func applyPublisher<T>(_ publisher: AnyPublisher<T, Never>) -> AnyPublisher<T, Never> { switch self { case .dropFirst(let count): return publisher.dropFirst(count).eraseToAnyPublisher() //skip the rest of cases }}}
Теперь оператор dropFirst
можно сохранить и отобразить в списке операторов.
Сохранение dropFirst
похоже на оператор and delay
. Возможно, фильтрация не так уж и отличается от служебных операторов. Что ж, давайте попробуем еще один оператор, прежде чем мы сделаем такой вывод.
Фильтр
В отличие от dropFirst
, который имеет очень простые критерии фильтрации, оператор filter
принимает замыкание вместо примитивного типа. Теперь это вызов. Как мы сохраняем и распространяем закрытие?
Давайте подробнее рассмотрим метод filter
.
func filter(_ isIncluded: @escaping (Self.Output) -> Bool) -> Publishers.Filter<Self>
Его закрытие isIncluded
принимает общий тип и возвращает логическое значение.
Есть ли в Foundation что-нибудь, что представляет логические условия и возвращает логическое значение? Звоните в колокола?
Фильтр с NSPredicate
Ответ - NSPredicate
. Если мы можем сохранить условия фильтрации как выражения в строковом формате, мы можем просто передать значение потока и использовать NSPredicate
для оценки результатов.
Давайте продолжим и добавим filter
в перечисление.
enum Operator { case delay(seconds: Double) case dropFirst(count: Int) case filter(expression: String) }
Все, что нам здесь нужно, это отфильтровать такие выражения, как %d !=3
или %@ != “D”
; следовательно, expression
- наш тип сотрудника. Точно так же нам нужно иметь возможность переместить перечисление filter
в Publisher.
extension Operator { func applyPublisher<T>(_ publisher: AnyPublisher<T, Never>) -> AnyPublisher<T, Never> { switch self { case .filter(let expression): return publisher.filter { value in NSPredicate(format: expression, argumentArray: [value]) .evaluate(with: nil) }.eraseToAnyPublisher() //skip the rest of cases }}}
Как и планировалось, мы отправляем выражение в NSPredicate
вместе со значением, отправленным в восходящем направлении от Издателя.
Обратите внимание, что NSPredicate
принимает массив аргументов. Поэтому с некоторыми изменениями он должен работать, даже если значения находятся в формате кортежа, что очень часто встречается в реактивных сценариях. Мы вернемся к этому в будущем, когда будем говорить о комбинированных операторах.
Как видите, поток фильтров добавляется в этот постоянный массив операторов и преобразуется в Publisher, чтобы отфильтровать число 3
из значений восходящего потока.
Следующий эпизод: Операторы постоянного преобразования, карта и сканирование
В демонстрационном GIF-изображении вы можете обнаружить, что список операторов довольно пуст. В ближайшие несколько недель мы собираемся заполнить их операторами разных типов: операторами преобразования, map
, и scan
.
Вы можете найти исходный код здесь, в репозитории comb-magic-swifui, в папке comb-площадка.