Error - 'main actor-isolated uses in closure may race..'

The Apple sample app PHPickerDemo (WWDC21 session 10046: 'Improve access to Photos in your app') after updating to Swift 6, in the ViewController extension method #displayNext() has the following error:
"Task-isolated 'livePhoto' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses"

The fix is eluding me and it has been a couple of days of head scratching...
Here is the code

let itemProvider = selection[assetIdentifier]!.itemProvider
        if itemProvider.canLoadObject(ofClass: PHLivePhoto.self) {
            progress = itemProvider.loadObject(ofClass: PHLivePhoto.self) { [weak self] livePhoto, error in
                DispatchQueue.main.async {
                    self?.handleCompletion(assetIdentifier: assetIdentifier, object: livePhoto, error: error)
                }
            }
        }

The #loadObject(ofClass: ..) does state "Asynchronously loads an object of a specified class ". Thus a boundary is being crossed from the background to the MainActor of the ViewController instance. The 'DispatchQueue.main.async' is the documented pattern for the NSItemProvider to interact with the UI

The completion handler of this method is marked with @Sendable parm - which should not be a race-- right???

completionHandler: @escaping @Sendable ((any NSItemProviderReading)?, (any Error)?) -> Void.

If the value captured in the completion handler is sendable why is there an error?
Also, the livePhoto object will not be modified by the background process after this return to the UI MainActor so I'd like to mark a 'sending' somewhere as another way to fix.. but how???

The error message " may race against later nonisolated uses" seems like a guess about the future use of the return value.. My understanding is that where the nonisolated use of the value occurs is where the compiler should be raising the error.. not here... right???

I am stumped, perplexed etc.. thanks in advance for any hints!!

p.s current production versions - xCode 16.2, iOS 18.2.1

corrected typo

Solution found by looking for types that are Sendable. I found that UIImage is Sendable but PHLivePhoto is not.
The Swift 6 compiler error from passing an object of type NSItemProviderReading is resolved by typecasting the result as UIImage BEFORE dispatching it back to the MainActor.

    if itemProvider.canLoadObject(ofClass: UIImage.self) {
        progress = itemProvider.loadObject(ofClass: UIImage.self) { image , error in
            if let myImage = image as? UIImage {
                DispatchQueue.main.async {
                    self.displayImage(myImage)
                }

            }
        }
    } 

The error on PHLivePhoto is skipped by regarding the LivePhotos as UIImages for the purposes of the demo app.

Now back to my own app which had the same issue as the PHPickerDemo.

The lack of response to my question made me dig much deeper into the documentation.

The most helpful was not the concurrency migration resources but rather the language proposals themselves - in particular Proposal 0302 'concurrent-value-and-concurrent-closures'. I highly recommend it to other concurrency neophytes.

Quick note: the link you provided points to a fork of the Swift Evolution repository and may not show the most up-to-date version of the proposal (though in this case, I don’t think you missed any substantial revisions). The official link to SE-302 is this one: SE-302: Sendable and @Sendable closures

1 Like