You should simply store the task into a local variable and capture it into the closure instead of self because even if you make the class sendable with a lock or use an actor, you'll introduce a potential race condition where cont.onTermination will cancel the wrong task.
I wonder how you are going using pollPodcast()? Is that from UI?
I find it easier to expose the state in observable model instead of a stream that I'd need to poll somehow.
Minimal example
@MainActor class PodcastManager: ObservableObject {
struct State {
var string: String
}
@Published var state = State(string: "")
private let session = URLSession(configuration: .default, delegate: nil, delegateQueue: .main)
init() {
poll()
}
func poll() {
var url: URL!
// get the change when it happens or timeout after one minute if there's no change
session.dataTask(with: url) { data, response, error in
MainActor.assumeIsolated {
if let error {
self.state.string = "..."
} else if let response, response.status >= 200 && response.status < 300 {
self.state.string = "..."
} else {
self.state.string = "..."
}
self.poll()
}
}.resume()
}
}
@MainActor struct MyView {
@StateObject private var model = PodcastManager()
var body: View {
Text(model.state.string)
}
}
I wonder how you are going using pollPodcast() ? Is that from UI?
My first attempt here was actually on the UI but it felt weird polling from the UI so I ditched that approach. Although I must admit at this point, other than separation of concerns, I don't have a strong preference for either approach. Only when the view gets more complicated would I really prefer to update UI logic inside from a Observable class to avoid unnecessary view re-renders.