There are some cases when code should be executed but its result should be not handled.
If I use completion blocks the code looks like the following:
func someFunc(completion: (() -> ())?) { ... }
someFunc(nil)
Now if I use Combine I should write something like this:
func someFunc() -> AnyPublisher<(), Never> { ... }
someFunc()
.sink { _ in
} receiveValue: {
}
.store(in: ...)
But is it more convenient solution like a class which implements Subscriber protocol but does nothing?
Why is it required? For example, preloading of some remote resource which you don't need display but need to cache.
"Functional Reactive Programming (FRP)" libraries (of which Combine is one example) accept a series of values that arrive over time, transform those values through functions, and deliver the final transformed result to a subscriber.
Ideally the transformation functions should be pure - have no side-effects.
The fundamental purpose of the library is to deliver the transformed values to a subscriber. As a result there is no built-in empty subscriber. That is the answer to your question and the reason behind it.
To put it another way, if the pipeline uses pure functions, and nobody cares about the result, the whole pipeline serves no purpose.
You are relying on intermediate functions in your pipeline that do have side-effects (are not "pure functions"). And you don't care about the final, transformed, values. Your usage is at odds with some of the fundamental tenets of FRP.
In my opinion, I think that is a code smell that you might be using the wrong tool for the job or you might not be using the tool correctly. From what you've described, it seems to me that your pipeline wants to deliver values to the cache. So the save-to-the-cache operation should probably happen in the
sink.At the very least, I would be inclined to have
sinkgenerate a log message about the success or failure of the operation. Side effects are to be expected insink.