Github.com/agelessman/…

This article focuses on time-dependent operators in Combine, which are powerful because pipline is an asynchronous flow.

debounce

The diagram above illustrates the basics of Debounce:

  • First set the time window duration, above is 0.5 seconds
  • Each time publisher sends new data, it reopens a time window and cancels the previous time window
  • If there is no new data after the end of the last open time window,debounceSend the data downstream

So what are its usage scenarios?

  1. Solve the problem that the search box frequently initiates network requests. When users input a character, they initiate network requests, which wastes some network resources and passesdebounce, can be implemented when the user stops typing 0.5 seconds before sending the request.
  2. To deal with the problem of continuous button clicking,debounceOnly the last click event after 0.5 seconds is received, so multiple consecutive click events in the middle are automatically ignored

The following code is an official example:

let bounces:[(Int.TimeInterval)] =[(0.0),
    (1.0.2),  // 0.2s interval since last index
    (2.1),     // 0.7s interval since last index
    (3.1.2),  // 0.2s interval since last index
    (4.1.5),   // 0.2s interval since last index
    (5.2)      // 0.5s interval since last index
]

let subject = PassthroughSubject<Int.Never>()
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)}}Copy the code

delay

Delay is a very simple Operator that allows pipline to wait a certain amount of time after receiving data from Publisher before sending it downstream.

As can be seen from the figure above, data was sent at 0.2 seconds and 0.5 seconds, but the final data was output at 1.2 seconds and 1.5 seconds.

The code is as follows:

let bounces:[(Int.TimeInterval)] =[(0.0.2),
    (1.0.5)]let subject = PassthroughSubject<Int.Never>()
cancellable = subject
    .delay(for: 1.0,
           scheduler: RunLoop.main)
    .sink { index in
        print ("Received index \(index) in \(Date().timeIntervalSince1970)")}for bounce in bounces {
    DispatchQueue.main.asyncAfter(deadline: .now() + bounce.1) {
        subject.send(bounce.0)
        print ("send \(Date().timeIntervalSince1970)")}}Copy the code

The output is as follows:

send 1606736282.5276031
send 1606736282.8222818
Received index 0 in 1606736283.528779
Received index 1 in 1606736283.822552
Copy the code

An official example:

let df = DateFormatter()
df.dateStyle = .none
df.timeStyle = .long
cancellable = Timer.publish(every: 1.0, on: .main, in: .default)
    .autoconnect()
    .handleEvents(receiveOutput: { date in
        print ("Sending Timestamp \ '\(df.string(from: date))\ ' to delay()")
    })
    .delay(for: .seconds(3), scheduler: RunLoop.main, options: .none)
    .sink(
        receiveCompletion: { print ("completion: \ [$0)", terminator: "\n") },
        receiveValue: { value in
            let now = Date()
            print ("At \(df.string(from: now)) received  Timestamp \ '\(df.string(from: value))\ ' sent: \(String(format: "%.2f", now.timeIntervalSince(value))) secs ago", terminator: "\n")})// Prints:
// Sending Timestamp '5:02:33 PM PDT' to delay()
// Sending Timestamp '5:02:34 PM PDT' to delay()
// Sending Timestamp '5:02:35 PM PDT' to delay()
// Sending Timestamp '5:02:36 PM PDT' to delay()
// At 5:02:36 PM PDT received Timestamp '5:02:33 PM PDT' sent: 3.00 secs ago
// Sending Timestamp '5:02:37 PM PDT' to delay()
// At 5:02:37 PM PDT received Timestamp '5:02:34 PM PDT' sent: 3.00 secs ago
// Sending Timestamp '5:02:38 PM PDT' to delay()
// At 5:02:38pm PDT received Timestamp '5:02:35pm PDT' sent: 3.00 secs ago
Copy the code

When timer.publisher calls.autoconnect(), this is triggered immediately, using delay if we want to delay for a few seconds.

measureInterval

MeasureInterval is also an interesting Operator that records the interval between publisher sending data.

Pictured above example, when the publisher to send data to zero, we obtain the time interval is about 0.2 seconds, while sending data 1, the time interval is about 1.3 seconds, measureInterval returns the type of data is SchedulerTimeType Stride, it represents the distance between the two.

Note that the time interval is not strictly accurate, with a range of deviations.

let bounces:[(Int.TimeInterval)] =[(0.0.2),
    (1.1.5)]let subject = PassthroughSubject<Int.Never>()
cancellable = subject
    .measureInterval(using: RunLoop.main)
    .sink { print($0)}for bounce in bounces {
    DispatchQueue.main.asyncAfter(deadline: .now() + bounce.1) {
        subject.send(bounce.0)}}Copy the code

Print result:

Stride(magnitude: 0.25349903106689453)
Stride(magnitude: 1.2467479705810547)
Copy the code

throttle

Throttle is a relatively simple Operator, as shown in the figure above, which sets a fixed interval time window of 0.5 seconds and sends data when the time window ends.

In the figure above, data is sent at 0.5 seconds, 1.0 seconds and 1.5 seconds.

Pay attention to,latestYou can specify whether the data to be sent is the first or last data in the window. When publisher sends data right at the edge of the time window, the results are inconclusive.

The code is as follows:

let bounces:[(Int.TimeInterval)] =[(1.0.2),
    (2.1),
    (3.1.2),
    (4.1.4),]let subject = PassthroughSubject<Int.Never>()
cancellable = subject
    .throttle(for: 0.5,
              scheduler: RunLoop.main,
              latest: true)
    .sink { index in
        print ("Received index \(index) in \(Date().timeIntervalSince1970)")}for bounce in bounces {
    DispatchQueue.main.asyncAfter(deadline: .now() + bounce.1) {
        subject.send(bounce.0)}}Copy the code

To understand Throttle, it’s best to compare it to debounce, which looks like this with the same code:

So to sum up,throttleArrange time Windows in a fixed order, send data at the end of the time window, anddebounceEach time new data is received, a new time window is reopened and the previous time window is cancelled.

Let me give you an example. Throttle can send data four times if I hit the button crazily within two seconds and the time window is 0.5 seconds long, whereas Debounce doesn’t send data and only sends once when I stop clicking for 0.5 seconds.

timeout

As shown in the figure above, timeout is used to set the pipline timeout, usually in seconds. Take a look at the code:

enum MyError: Error {
    case timeout
}

let subject = PassthroughSubject<Int.Never>()
cancellable = subject
    .setFailureType(to: MyError.self)
    .timeout(.seconds(1),
             scheduler: RunLoop.main,
             customError: {
        MyError.timeout
    })
    .sink(receiveCompletion: { print($0) }, receiveValue: { print($0)})DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) {
    subject.send(1)}Copy the code

The first two arguments of timeout are nothing to write home about. The third argument is interesting. When a timeout occurs, customError is called and returns an error.

SetFailureType (to: publisher, publisher, publisher, publisher, publisher, publisher, publisher, publisher, publisher) Myerror. self) set the Never error type of PassthroughSubject

() to MyError.
,>

So now a new question arises, what happens if you assign customError to nil?

.timeout(.seconds(1),
         scheduler: RunLoop.main,
         customError: nil)
Copy the code

Print result:

finished
Copy the code

As you can see, if customError is specified, the Pipline returns an error in that closure, and if not, the Pipline ends normally.

As shown below: