Combine Timeout

Derrick Ho
2 min readJun 28, 2021

Combine makes it easy to make your subscriber auto-cancel after a timeout.

let exampleNotification = Notification.Name("com.example")
NotificationCenter.default.publisher(for: exampleNotification, object: nil)
.timeout(.seconds(3), scheduler: DispatchQueue.main)
.subscribe(Subscribers.Sink(receiveCompletion: { result in
print("completion", result)
}, receiveValue: { value in
print("value", value)
}))

There are a couple of things that might not be obvious.

  • The timeout is the amount of time after the last post. If there are zero posts then it would timeout in exactly 3 seconds (or whatever you’ve specified). But if you received a post after 2 seconds then it would timeout 3 seconds after that. It is kind of like a debounce
  • If you timeout the stream will finish. If you want the first post to make it end, use .first() then both paths will end up with a receive completion result of .finished .
  • However if you set the failure type and use a custom error the stream will end in .failure . If you did not use .first() , even if you received a few posts, the timeout will still happen and it will end in failure.
  • If you receive the post it will hit the receive value block followed by the receiveCompletion block. If you don’t and it times out it skips the receive value block. This was surprising to me because I expected timeouts to be some kind of error. If you want to have special logic for the timeout state you’d have to take advantage of this behavior. For example you can add a boolean flag that gets set in the receiveValue block and you can check its result in the receive completion block.
var timedOut = true
let exampleNotification = Notification.Name("com.example")
NotificationCenter.default.publisher(for: exampleNotification, object: nil)
.timeout(.seconds(3), scheduler: DispatchQueue.main)
.subscribe(Subscribers.Sink(receiveCompletion: { result in
if timedOut { handleTimeout() }
}, receiveValue: { value in
timedOut = false
}))

--

--