Saving filtering logic in Swift Combine. Part 2

Data Oriented Combine





The translation of the article was prepared especially for students of the advanced course "iOS Developer" .





In the previous episode, we successfully modeled a stream of values, where one simple operator ( delay) was attached to each value .

In this part, we will look at a few more operators, make them, Codeableand finally convert them to Combine publisher at runtime.



Operator Types


Before we begin to model them, we need to understand what types of operators exist.

The ReactiveX site breaks them down into about 10 categories: creation, transformation, filtering, combining, error handling, auxiliary, conditional, mathematical / aggregate, backpressure, connectable-observable and observable (To) conversion operators. If you're interested, ReactiveX has a good explanation for each type and operator.
Note: If you are not familiar with RxSwift, Observable in RxSwift is equivalent to Publisher in Combine.
In the previous part, we mentioned the operator delay, which refers to the auxiliary ( utility) type. Today we will focus on preserving two filtering ( filtering) operators.



Filter operators


This type of operator prevents all or some (or none) of the flow elements from moving further upstream based on this condition.

dropFirst


dropFirststops the transmission of the first nelements. We can simply add it to our enum Operator, given its simplicity.

enum Operator {
 case delay(seconds: Double)
 case dropFirst(count: Int)
}

We can also easily convert this listing case to Publisher.

extension Operator {func applyPublisher<T>(_ publisher: AnyPublisher<T, Never>) -> AnyPublisher<T, Never> { 
switch self {
   case .dropFirst(let count):
       return publisher.dropFirst(count).eraseToAnyPublisher()
 //   
 }}}

Now the operator dropFirstcan be saved and displayed in the operator list.



Saving dropFirstis similar to an operator delay. Perhaps the filtering is not so different from the auxiliary operators. Well, let's try another statement before we make such a conclusion.

Filter


In contrast dropFirst, which has very simple filtering criteria, the operator filteruses closure instead of a primitive type. This is a more complicated case. How do we save and spread a closure?

filter(_:)

Re-publishes all elements that match the given closure.

developer.apple.com


Let's take a filtercloser look at the method .

func filter(_ isIncluded: @escaping (Self.Output) -> Bool) -> Publishers.Filter<Self>

Its closure isIncludedtakes a universal type and returns a boolean value.

Is there anything in Foundation that represents logical conditions and returns a logical value? Reminds me of something?

Filter with NSPredicate


The answer is NSPredicate. If we can save the filtering conditions as expressions in string format, we can simply pass the value of the stream and use it NSPredicateto evaluate the results.

Let's continue and add filterto the listing.

enum Operator {
 case delay(seconds: Double)
 case dropFirst(count: Int)
 case filter(expression: String)
}

All we need to do is filter expressions like %d !=3or %@ != β€œD”; therefore, expression is an appropriate type. Similarly, we should be able to move the listing filter to 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()
      
     //   
 }}}

As planned, we send the expression to NSPredicatealong with the value sent from Publisher.

Note that NSPredicateaccepts an array of arguments. Therefore, it should work with some modification, even when the values ​​assume the format of tuples, which is very common in reactive scenarios. We will come back to this in the future when we talk about combined operators.



As you can see, Filter Stream is added to this stored array of statements and converted to Publisher to filter the number 3 from the higher values.



In the next episode: Saving Transformation Operators, Map and Scan


In the GIF demo, you may find that the list of operators is pretty empty. In the next few weeks, we are going to fill it with various types of operators: transformation operators, mapand scan.
You can find the source code in this combine-magic-swifui repository in the combine-playground folder.

We look forward to your comments and invite you to open day at the course .

All Articles