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, Codeable
and 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
dropFirst
stops the transmission of the first n
elements. 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 dropFirst
can be saved and displayed in the operator list.
Saving dropFirst
is 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 filter
uses 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 filter
closer look at the method .func filter(_ isIncluded: @escaping (Self.Output) -> Bool) -> Publishers.Filter<Self>
Its closure isIncluded
takes 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 NSPredicate
to evaluate the results.Let's continue and add filter
to 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 !=3
or %@ != β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 NSPredicate
along with the value sent from Publisher.Note that NSPredicate
accepts 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, map
and 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 .