replaceNil(with:)

As shown in the pinball diagram below, replaceNil will accept the optional value and replaceNil with the value you specify

To test its functionality, type the following code into playground:

["A".nil."C"].publisher	/ / 1
    .eraseToAnyPublisher()
    .replaceNil(with: "-") / / 2
    .sink(receiveValue: { print($0)})/ / 3
    .store(in: &subscriptions)
Copy the code
  1. Create Publisher from an array of optional strings.
  2. Use replaceNil(with:) to receive from upstream publishersnilValue is replaced by"-"
  3. print

The print result is as follows

A
-
C
Copy the code

Use the “??” There is an important difference between operators and replaceNil:

“??” Operators can get a nil result, but replaceNil does not

replaceEmpty(with:)

You can use the replaceEmpty(with:) operator to replace a value (if the publisher is done but does not issue a value). In the pinball diagram below, the publisher completes without emitting anything, at which point the replaceEmpty(with:) operator inserts a value and publishes it downstream

Enter the following code in playground:

/ / 1
  let empty = Empty<Int.Never> ()/ / 2
  empty
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })
    .store(in: &subscriptions)
Copy the code
  1. Create an empty publisher that issues the completion event immediately.
  2. Subscribe to it, and print the events you receive

Use the Empty publisher type to create a publisher that issues the.finished event immediately. You can also configure it to never emit anything by passing false to its completeImmediately parameter (true by default).

Empty is useful for demonstration or testing purposes, or when all you want to do is signal to subscribers that some task is complete.

Run playground and you will see it succeed

finished

Now, insert this line of code before calling sink

.replaceEmpty(with: 1)
Copy the code

Run playground again, and you’ll see the following print

1
finished
Copy the code

Incremental conversion data

You’ve seen how Combine includes operators such as Map, which correspond to and work similarly to higher-order functions in the Swift standard library. However, Combine has a few other tricks that let you manipulate the values you receive from upstream publishers.

scan(:🙂

A good example of a transformation category is scanning. It feeds the closure the current value emitted by the upstream publisher, as well as the last value returned by the closure. In the pinball diagram below, the scan starts by storing a starting value of 0. As it receives each value from the publisher, it adds it to the previously stored value, then stores and emits the result

The original Demo in the book is a little complicated, but I present a simpler Demo here, which can more intuitively introduce the usage of SCAN

Enter the following code in playground:

_ = [3.4.5].publisher		/ / 1
	/ / 2
    .scan(2) { initValue, indexValue -> Int in
        print("initValue = \(initValue), indexValue = \(indexValue)")
        return initValue * indexValue
    }
    .sink(receiveCompletion: { completion in
        print("completion: \(completion)")
    }, receiveValue: { value in
		/ / 3
        print("receiveValue: \(value)")})Copy the code
  1. Create a Publisher from the Int array.
  2. We want to use it herescanComputes the product of each element in the array, multiplying by the result of the initial value (i.e(3 * 4 * 5) * 2The result of)
  3. Print the results received by sink each time

Run playground and see the following print:

initValue = 2, indexValue = 3
initValue = 6, indexValue = 4
initValue = 24, indexValue = 5
receiveValue: 6
receiveValue: 24
receiveValue: 120
completion: finished
Copy the code

I hope you can observe the print result first and explain the usage of scan parameters at the same time, so that it is more intuitive.

The scan initialization function is

public func scan<T> (_ initialResult: T._ nextPartialResult: @escaping (T.Publishers.Sequence<Elements.Failure>.Output) - >T) -> Publishers.Sequence"[T].Failure>
Copy the code

As you can see, it takes three arguments, one for the initial value to be set, one for the next value to be traversed, and finally a @escaping closure to perform data processing.

Then let’s go back to the output of our playground:

  • The first three lines representscanRan three times, because Publisher’s Output is an array of three data.
  • scanEvery timeThe initial value * Next Output dataAnd the results are then stored toThe initial valueIn the
  • At the end of three cycles,scanPartially completed, enteringsinkphase
  • sinkIn, we do not receive the final result (120) at one time, but also divided into three receives. becausescanThe Publisher type returned by the Publisher operator isPublishers.Sequence<[T], Failure>

So while our demo is calculating the result of multiplying the elements of an array, scan is actually not the best choice for calculating this result

Key Points in this chapter

  • The method that you call to operate on the publisher’s output is “operator.”
  • Operators are also publishers.
  • Conversion operators convert input from upstream publishers into output suitable for downstream use.
  • Pinball diagrams are a good way to visualize how each combined operator works.
  • Be careful when using any of the buffered operators (such as Collect or flatMap) to avoid memory problems.
  • Be careful when applying the functional knowledge already in the Swift standard library. Some combinatorial operators with similar names work the same way, while others work completely differently.
  • It is common to link multiple operators together in a subscription to create complex composite transformations of events emitted by the publisher