Комбинирование на основе данных

В предыдущем эпизоде мы успешно смоделировали поток значений и добавили один простой оператор (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 (_ :)
Повторно публикует все элементы, соответствующие указанному закрытию. developer.apple.com



Давайте подробнее рассмотрим метод 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-площадка.