Unikorn
Unikorn

Reputation: 1151

How do you run a Swift Combine Publisher for a certain amount of time?

I have a publisher when the sink, scans for a list of wifi. I only want to scan for about 10 seconds and stop.

Is there a way to do this within the publisher chain of calls?

Upvotes: 7

Views: 3370

Answers (2)

Daniel T.
Daniel T.

Reputation: 33967

This operator will do the trick.

import PlaygroundSupport
import Foundation
import Combine

let page = PlaygroundPage.current
page.needsIndefiniteExecution = true

extension Publisher {
    func stopAfter<S>(_ interval: S.SchedulerTimeType.Stride, tolerance: S.SchedulerTimeType.Stride? = nil, scheduler: S, options: S.SchedulerOptions? = nil) -> AnyPublisher<Output, Failure> where S: Scheduler {
        prefix(untilOutputFrom: Just(()).delay(for: interval, tolerance: tolerance, scheduler: scheduler, options: nil))
            .eraseToAnyPublisher()
    }
}

let source = Timer.publish(every: 1, tolerance: nil, on: RunLoop.main, in: .default, options: nil)
    .autoconnect()
    .eraseToAnyPublisher()

let cancellable = source
    .stopAfter(10, scheduler: DispatchQueue.main)
    .sink(receiveValue: { print($0) })

Upvotes: 9

Cristik
Cristik

Reputation: 32817

You can use the timeout() operator:

Terminates publishing if the upstream publisher exceeds the specified time interval without producing an element.

wifiScannerPublisher
    .timeout(.seconds(waitTime), scheduler: DispatchQueue.main, options: nil, customError:nil)
    .sink(
        receiveCompletion: { print("completion: \($0), at: \(Date())") },
        receiveValue: { print("wifi: \($0)") }
     )

If, however your publisher keeps regularly emitting events, and you just want to stop it after an amount of time passes, then Daniel's answer is probably the way to go.

Nonetheless, I'll add a solution on my own, via a Publisher extension that uses timeout() and scan():

extension Publisher {
    func stopAfter(_ interval: TimeInterval) -> AnyPublisher<Output, Failure> {
        self
            .timeout(.seconds(interval), scheduler: DispatchQueue.main)
            .scan((Date()+interval, nil)) { ($0.0, $1) }
            .prefix(while: { Date() < $0.0 })
            .map { $0.1! }
            .eraseToAnyPublisher()
    }
}

The above publisher will carry the timeout date, and once that date is reached, it will stop. The map is needed to discard the extra Date carried along the items.

Usage:

wifiListPublisher.stopAfter(10)
    .sink(...)

Upvotes: 3

Related Questions