Leave a rating/review
The timeout operator is a special timing operator whose purpose is to semantically distinguish an actual timer from a timeout condition. Therefore, when a timeout operator fires, it either completes the publisher or emits an error you specify. In both cases, the publisher terminates. Let’s look at timeout in a demo.
Switch to the Timeout page in the playground. Add a PassthroughSubject that sends values of type Void, which is actually a legitimate type, just sending a notification that something happened. Apply a timeout operator on the subject that times out after 5 seconds, and runs on the main queue.
let subject = PassthroughSubject<Void, Never>()
// 1
let timedOutSubject = subject.timeout(.seconds(5), scheduler: DispatchQueue.main)
Make a new TimelineView with the title “Button taps”
let timeline = TimelineView(title: "Button taps")
Make a VStack that has a Button inside it. Make the button’s call the subject’s send method when pressed, and set the text of the Button to “Press me within 5 seconds”. Below the button, place the timeline.
let view = VStack(spacing: 100) {
// 1
Button(action: { subject.send() }) {
Text("Press me within 5 seconds")
}
timeline
}
Set the view playground’s live view.
PlaygroundPage.current.liveView = UIHostingController(rootView: view)
Finally, instruct the timedOutSubject to display events in the timeline.
timedOutSubject.displayEvents(in: timeline)
Run the playground. If you don’t do anything within a 5 second interval, the publisher will timeout and complete. Run the playground again, but this time tap the button at less than 5 second intervals. This time, the publisher never times out, so it never completes.
One last thing you can do here is have the publisher emit an error when the timeout happens. Make a new TimeoutError enum that extends Error, and give it a timedOut case.
enum TimeoutError: Error {
case timedOut
}
Change the subject’s error type from Never to TimeoutError
let subject = PassthroughSubject<Void, TimeoutError>()
Finally, in the timeout operator call, add the customError argument and pass in the .timedOut case.
let timedOutSubject = subject.timeout(.seconds(5),
scheduler: DispatchQueue.main,
customError: { .timedOut })
Run the playground again and let the publisher timeout. You’ll see that the timedOutSubject emits a failure.
The last timing operator is measureInterval. This operator doesn’t manipulate time but just measures it. The measureInterval(using:) operator is used to find out the time that elapsed between two consecutive values emitted by a publisher. Let’s look at it in a demo.
Go to the MeasureInterval page in the playground. Make a PassthroughSubject that has a String input type, and add a measureInterval operator on that subject, run it on the main queue.
let subject = PassthroughSubject<String, Never>()
// 1
let measureSubject = subject.measureInterval(using: DispatchQueue.main)
Make 2 Timeline views, one for “Emitted values” and another for “Measured values”. Add those to a VStack and set the playground’s live view to a UIHostingController that uses that VStack as the root.
let subjectTimeline = TimelineView(title: "Emitted values")
let measureTimeline = TimelineView(title: "Measured values")
let view = VStack(spacing: 100) {
subjectTimeline
measureTimeline
}
PlaygroundPage.current.liveView = UIHostingController(rootView: view)
Instruct the publishers to display events in their respective timelines.
subject.displayEvents(in: subjectTimeline)
measureSubject.displayEvents(in: measureTimeline)
Make 2 sink subscriptions, 1 for each publisher. Print the delta time and emitted values to console.
let subscription1 = subject.sink {
print("+\(deltaTime)s: Subject emitted: \($0)")
}
let subscription2 = measureSubject.sink {
print("+\(deltaTime)s: Measure emitted: \($0)")
}
Use the typingHelloWorld array from the playground supplemental source to feed the PassthroughSubject.
subject.feed(with: typingHelloWorld)
Run the playground. In the console you’ll see the messages from each sink, showing the delta time and emitted string and interval. The interval values look odd though - that is because they are in the form of a TimeInterval in nanoseconds! You can fix that by updating the print statement:
let subscription2 = measureSubject.sink {
print("+\(deltaTime)s: Measure emitted: \(Double($0.magnitude) / 1_000_000_000.0)")
}
Rerun the playground, and you’ll see the values are now in seconds. You can also run on a different queue. Make a new measureInterval subject to run on the main RunLoop
let measureSubject2 = subject.measureInterval(using: RunLoop.main)
There is no need to make an additional Timeline view. Make a new subscription to print the times from measureSubject2.
let subscription3 = measureSubject2.sink {
print("+\(deltaTime)s: Measure2 emitted: \($0)")
}
Run the playground. You’ll see that the values are displayed directly in seconds, but different from the intervals printed when running on the main queue. It is probably best to stick with DispatchQueue for the scheduler, but in the end it is up to you.
You’ve made it through all of the timing operators for this course! Let’s recap.
delay delays the values emitted by a publisher so that you can see them later than they actually occur, and collect collects a series of values over a period of time before performing an operation, which is useful when performing operations like averages.
debounce allows you to wait for values to accumulate before passing them downstream to a consumer; throttle is like debounce, but lets you run on other schedulers and emit either the first or last value emitted from the publisher.
timeout sets up a timeout for the publisher; if a value isn’t sent within the specified time interval, the publisher times out and completes; measureInterval measures the interval between emissions from the publisher.
With all of those timing operators under your belt, it looks like a challenge is up ahead! Go take a quick break, and when you’re ready, come back to put your skills to the test.