Consider an object like this that publishes values via a subject:
class IntStream {
let subject = PassthroughSubject<Int, Never>()
var publisher: AnyPublisher<Int, Never> {
subject.eraseToAnyPublisher()
}
init(_ times: Int = 5) {
for i in 0..<times {
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + .seconds(i)) { [weak self] in
self?.subject.send(i)
if i == times - 1 {
self?.subject.send(completion: .finished)
}
}
}
}
}
This works great, but the consumer is now required to hold onto the subscription and also the IntStream (the object that retains the PassthroughSubject).
If the consumer doesn't hold onto their reference to IntStream, then the subject is dealloc'd and values are not published.
My question is if this is expected? The reason I ask is because with custom publishers that I've written, I've been able to have the custom Subscription class hold onto all the state necessary to publish values, and in those cases, it's not necessary to hold onto the object that vends the publisher. What is the best practice here?
It prints a message when it is destroyed, so we can see if it is destroyed at the correct time.
It returns a publisher that uses map to hold a reference to self, which should keep the IntStream from being destroyed if there are any live subscriptions.
So we can see that each subscription continues for the correct amount of time, and the IntStream is destroyed after all subscriptions have been cancelled.
I only tested this in a playground. Maybe in an optimized build, that self reference in the map closure gets optimized out. In that case, you'd need to use withExtendedLifetime to defeat the optimizer.
@mayoff - Thank you for taking the time to answer my question. That's a clever solution!
I don't know about “best practice” because Apple has given little guidance about writing your own Publisher or managing this problem.
I don't see any downsides to doing it this way, other than if a consumer of the api expected the stream to stop if you release all references to it. Maybe I should test the URLSession.DataTaskPublisher to see what happens if you release all references to the URLSession.
Good point! Looking at the source it looks like the Subscription holds onto a copy of the DataTaskPublisher, which retains the Session. So I guess that answers that. Thank you!