The core idea behind reactive programming is to model the flow of asynchronous events over time. In this regard, the Combine framework provides a set of operators that let you handle time. In particular, how sequences react to and transform values over time.

delay

Combine has some time operators. The most basic time operator, delay, delays values from the publisher so that you see them later than they actually happened.

Delay (for: how: scheduler: the options) operator on the entire value sequences to move:

Each time an upstream publisher issues a value, delay holds it for a while, and then issues it on the scheduler you specify after the delay you request

The sample code here in the original book is a bit more complex and fancy. We use the simplest demo here to show what these time operators can do.

Add the following code to our playground:

/ / 1
let timerPublisher = Timer
  .publish(every: 1.0 , on: .main, in: .common)
  .autoconnect()


timerPublisher
    / / 2
    .handleEvents(receiveOutput: { date in
           print ("Sending \(date) to delay()")})/ / 3
    .delay(for: .seconds(2), scheduler: DispatchQueue.main)
    .sink { value in
        / / 4
        print("Receive \(value)")
    }
    .store(in: &cancellable)
Copy the code
  1. Create a Timer Publisher
  2. Subscribe to timer Publisher and debug the timer published data, in this case Date data
  3. After a delay of 2 seconds, the data is published downstream
  4. Prints the date data received

Run playground to get the result:

Sending 2021-10-14 09:23:04 +0000 to delay()
Receive 2021-10-14 09:23:02 +0000
Sending 2021-10-14 09:23:05 +0000 to delay()
Receive 2021-10-14 09:23:03 +0000
Sending 2021-10-14 09:23:06 +0000 to delay()
Receive 2021-10-14 09:23:04 +0000
Copy the code

As you can see, there is a 2 second difference between the published date and the accepted date.

Collecting values

In some cases, you may need to collect data from publishers at specified intervals. This is a useful form of buffering. For example, when you want to average a set of values over a short period of time and print the average.

collect(_:options:)

The elements are collected by a given time grouping policy and a single array of collections is emitted.

Note that this operator is different from collect in chapter 3, where collect is declared as collect(_:)

Use Collect (_:options:) to emit an array of elements according to the schedule specified by the Scheduler and Stride that you provide. At the end of each predetermined interval, the publisher sends an array containing the items it collected.

If the upstream publisher finishes before populating the buffer, the publisher sends an array containing the items it received. This may be less than the number of elements specified in the requested step. If the upstream publisher fails due to an error, the publisher forwards the error to the downstream receiver instead of sending its output.

The above example collects the timestamps generated on a one-second timer in a group of five (Stride).

Here we use an example from Apple’s official documentation to illustrate:

let sub = Timer.publish(every: 1, on: .main, in: .default)
    .autoconnect()
    .collect(.byTime(RunLoop.main, .seconds(5)))
    .sink { print("\ [$0)", terminator: "\n\n")}// Prints: "[2020-01-24 00:54:46 +0000, 2020-01-24 00:54:47 +0000,
// 2020-01-24 00:54:48 +0000, 2020-01-24 00:54:49 +0000,
//          2020-01-24 00:54:50 +0000]"
Copy the code
  • In this example, we use timer Publisher to publish data every second.
  • SchedulerSet toRunLoop.mainThe main thread,StrideSet it to 5 seconds.
  • collect(_:options:) The operator collects data for Publisher releases in five seconds, and because Publisher publishes once per second, it is collected five times and stored in an array
  • 5 seconds later, Sink begins to receive data, which is the array content collected by Collect

Debounce

Publish elements only after a specified time interval between events. Even though Publisher publishes many elements within the time interval, this operator allows us to filter out other elements and publish the last element within the time interval.

Debounce and later Throttle were both designed to “dejitter”. In our program, we can imagine that if we click a button, a network request will be sent. But if we hit the Button multiple times in a short amount of time, how can we efficiently handle these requests? A common design approach is to process only the last click event, discarding previous “invalid” click events. This is where these two operators come into play

In the figure above we can see:

  • At 0.1 and 0.2 seconds, Publisher publishes 1 and 2 Int data, respectively
  • At 1.1 and 1.2 seconds, Publisher published 5 and 6 Int data, respectively
  • Publisher is processed with the debounce operator at 0.5 seconds

Here we rewrite the Official Apple documentation to implement this operator schematic, because the original book is too complex.

Use the Debounce (for: Scheduler :options:) operator to control the number of values and the time between delivery of values from upstream publishers. This operator is useful for handling a burst or high-volume stream of events, where you need to reduce the number of values passed downstream to the rate you specify.

Let’s look at the code:

let bounces:[(Int.TimeInterval)] =[(1.0.1),   // release 1 in 0.1 seconds
    (2.0.2),   // release 2 in 0.2 seconds
    (5.1.1),   // 1.1 seconds to publish 5
    (6.1.2)    // 1.2 seconds release 6
]

let subject = PassthroughSubject<Int.Never> ()var cancellable = subject
    .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
    .sink { index in
        print ("Received index \(index)")}for bounce in bounces {
    DispatchQueue.main.asyncAfter(deadline: .now() + bounce.1) {
        subject.send(bounce.0)}}// Prints:
// Received index 2
// Received index 6
Copy the code

In the for loop, the loop publishes data with PassthroughSubject, the timing of publication being delayed by the second element of the specified tuple. Also, set the delay for Debounce to 0.5 seconds. For the first 0.5 second, 1 and 2 are released, and debounce works, keeping only the last release value 2 and sending it to sink. For the second 0.5 second, 5 and 6 are released, and debounce works, keeping only the last release value 6 and sending it to sink

In the demo we set the debounce delay to 0.5 seconds, meaning that debounce will only receive the last published data within 0.5 seconds. That is, to reduce data passing downstream to the frequency you specify.

Let’s look at one more practical use of this operator, such as our search page. On the search page we have a search box and a list of search results. When we enter the search keyword in the search box, we start the Combine process according to the keyword change, retrieve the local data that matches the search keyword and display it in the list of search results. When the user types too quickly, it is possible that each input element will generate a search request, wasting CPU processing (or network resources if the search is a network request). In this case, we can use debounce to delay the time between the user typing and the start of the search and filter out excess data. It only processes the last release of data after the delay.

Code snippet:

$searchText
	.debounce(for: .sencond(0.5), scheduler: RunLoop.main)
Copy the code

Let’s compare Delay and Debounce again:

  • delayIt is simply a delay in publishing upstream data to downstream
  • debounceIn addition to delaying the release of upstream data, some data is filtered out and only the last data published after the delay is published.

Throttle

Publishes the latest or first element published by an upstream publisher within a specified interval.

throttle (latest=false)

throttle (latest=true)

As you can see, Throttle is similar to debounce except that it has an additional parameter, “latest”, which, when throttle is true, returns the same value as debounce, which is published last, and when it is false, which is published first.

We also use Apple’s official documentation and sample code for this operator.

Use Throttle (for: Scheduler :latest:) to selectively republish elements from upstream publishers at the time interval you specify. Other elements received upstream within the restricted interval are not republished. In the example below, timer.timerPublisher generates elements at one-second intervals; The Throttle(for: Scheduler :latest:) operator passes the first event and then republishes only the latest event for the following ten-second interval:

cancellable = Timer.publish(every: 3.0, on: .main, in: .default)
    .autoconnect()
    .print("\(Date().description)")
    .throttle(for: 10.0, scheduler: RunLoop.main, latest: true)
    .sink(
        receiveCompletion: { print ("Completion: \ [$0).") },
        receiveValue: { print("Received Timestamp \ [$0).")})// Prints:
 // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:26:57 +0000)
 // Received Timestamp 2020-03-19 18:26:57 +0000.
 // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:27:00 +0000)
 // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:27:03 +0000)
 // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:27:06 +0000)
 // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:27:09 +0000)
 // Received Timestamp 2020-03-19 18:27:09 +0000.
Copy the code

timeout

func timeout<S> (_ interval: S.SchedulerTimeType.Stride.scheduler: S.options: S.SchedulerOptions? = nil.customError: (() - >Self.Failure)? = nil) -> Publishers.Timeout<Self.S> where S : Scheduler
Copy the code

A timeout operator that, when triggered by timeout, either completes the publisher or emits an error that you specify. In both cases, Publisher will be discontinued.

We use code from Apple’s official documentation to demonstrate this feature:

var WAIT_TIME : Int = 2
var TIMEOUT_TIME : Int = 5

let subject = PassthroughSubject<String.Never> ()let cancellable = subject
	/ / 1
    .timeout(.seconds(TIMEOUT_TIME), scheduler: DispatchQueue.main, options: nil, customError:nil)
    .sink(
          receiveCompletion: { print ("completion: \ [$0) at \(Date())") },
          receiveValue: { print ("value: \ [$0) at \(Date())")})/ / 2
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(WAIT_TIME),
                              execute: { subject.send("Some data - sent after a delay of \(WAIT_TIME) seconds")})// Prints: value: Some data - sent after a delay of 2 seconds at 2020-03-10 23:47:59 +0000
// completion: finished at 2020-03-10 23:48:04 +0000
Copy the code
  1. PassthroughSubject publishes String elements and is configured to timeout if no new elements are received within 5 seconds (TIMEOUT_TIME).
  2. Publish a single value (once only) after the specified 2 seconds (WAIT_TIME)

The last printed result is the value received for the first publication, and printed out after 5 seconds

We don’t use the customError parameter of timeout in the code, which defaults to nil, so we print completion: Finished. If you provide a closure for the customError parameter, the upstream publisher terminates upon timeout with an error message.

measureInterval(using:options:)

Measure and emit the time interval between events received from upstream publishers.

Let’s demonstrate this with code from Apple’s official documentation:

cancellable = Timer.publish(every: 1, on: .main, in: .default)
    .autoconnect()
    .measureInterval(using: RunLoop.main)
    .sink { print("\ [$0)", terminator: "\n")}// Prints:
/ / Stride: (magnitude 1.0013610124588013)
/ / Stride: (magnitude 0.9992760419845581)
Copy the code

In the above example, a 1-second timer is used as a data source for the event publisher; The measureInterval(using:options:) operator reports the elapsed time between receiving events on the main running loop

In this chapter are the Key Points

  • Combine’s handling of asynchronous events extends to the operation time itself.
  • Time series operators allow you to abstract work over long periods of time, rather than just processing discrete events.
  • You can usedelayOperator to delay processing the Combine process.
  • collectIt can act like a dam, “collecting” water and releasing it all at once.
  • debounceandthrottleEasy to select a single value over time.
  • timeoutProvides timeout handling.
  • Time is availablemeasureIntervalTo measure the