A "global" download manager with progress Effects to optional views

I wanted to ask for some suggestions as to best handle this situation. This is effectively a long-living Effect, but no one may be listening. Ideally I'd like to 'attach' and 'detach' from what is going on.

An example scenario: A user starts a download from an optional view. They navigate back to some root view, then go into another view (optional or otherwise), I'd like that new view to 'register' to know if any of the items are being downloaded and their progress.

Right now, I need to tear down the download Effect when the user leaves the optional view, but obviously that's not ideal.

Not sure if you have seen this yet, but we actually have a case study demonstrating how to interface with long living downloads. It doesn't do exactly what you want, but we essentially have a long living effect for initiating a download.

I think you can adapt this code to do what you want. What you could do is remove the lines that observe the progress (here) and instead introduce a new endpoint to the download client that can start the observation. Something like:

observe: { id in
  .run { subscriber in 
    guard let task = dependencies[id]?.task else { 
      subscriber.send(completion: .finished)

    let observation = task.progress.observe(\.fractionCompleted) { progress, _ in

    // TODO: handle subscriber completion

    return AnyCancellable { 

I haven't tried any of this, but I think something like this could work.

Thanks, that looks straightforward enough.

As a related aside, I have seen the TCA download example, and used in my code, I noticed that even when I cancel the Effect when an optional view goes out of scope, if a progress is in flight, I can still get an assertion in the TCA code that state is nil. If I delay by a second or 2 then nil out the optional state all is well, but that's not ideal of course.

With that said, perhaps this separation of the progress updates from the download client will resolve that issue for me anyway.

Ah interesting, there's probably some edge cases we should clean up in that demo. If you have any example code that reproduces the issue we could try to find a nice way to address it (either here or in an issue on the repo).