How do i avoid this warning about a captured var? (concurrency issue)

var progressObserver: ProgressObserver!


let task = self.dataTask(with: urlRequest) { data, response, error in
      progressObserver.shutdown()
      // other stuff
}

progressObserver = ProgressObserver(sessionTask: task)

task.resume()

The above yields this warning:
Reference to captured var 'progressObserver' in concurrently-executing code; this is an error in Swift 6

But here's the issue. My progressObserver class needs access to dataTask for what it does. But I can't create dataTask, which needs a closure in its initializer, without referring to progressObserver, so I figured I'd need to indirect.

Note the sequence:

  1. task is initialized with a closure which will in the future refer to progressObserver, which is currently nil. But that closure isn't called yet.
  2. After getting task initialized, I can construct my progressObserver which now references task.
  3. Finally, I call task.resume(), which will eventually cause the passed in closure to be called, but as I said, not until after (2) occurred.

I know the compiler can't infer this. But it works and I don't see any other reasonable solution.

A. Is there a different/better way to do this?
B. If not, is there something I can to tell the compiler "this is really OK"?

A very similar piece of code yields this warning as well:
'progressObserver' mutated after capture by sendable closure

I assume it's the same thing.

1 Like

Would this work for you?

let progressObserver = ProgressObserver()

let task = self.dataTask(with: urlRequest) { data, response, error in
      progressObserver.shutdown()
      // other stuff
}

progressObserver.sessionTask = task
task.resume()

Doh. That seems totally obvious.

I will try that. Perhaps something that simple didn’t occur to me because I had a solution that worked (well, until Swift compiller started complaining, this code is 3-4 years old now), and I felt like “why should I have to refactor my class this way”?

Thank you tera, yes, that works perfectly. Two-phase initialization, just making the internal members of ProgressObserver var's, that can be updated later.

All we do is move the indirection inside the class, so it can be set later.

How about this case?

        var progressObservation: NSKeyValueObservation

        let task = networkSession.dataTask(with: request) { [weak self] data, _, error in
            defer {
                self?.progressObservations.remove(progressObservation)
            }
           // other stuff
        }

        progressObservation = task.progress.observe(\.fractionCompleted) { progress, _ in
            // stuff with progress
        }
        progressObservations.insert(progressObservation)

        task.resume()
2 Likes