Search code examples
swiftasync-awaitsendableswift6

How to handle Non Sendable Struct in Async/Await?


I have a View that can let the User pick an Image. I bind the PhotoPicker to "item" a PhotoPickerItem. But in the "getImage" Function i get an error: "Sending 'photoPickerItem.some' risks causing data races".

I think the problem here is that PhotoPickerItem is not a Sendable. But how do i fix that issue? Waiting for Apple to let Photopickeritem conform to Sendable?


@MainActor
struct EditableImage: View {
    
    var selectedImageUrl: Binding<String?>
    var imageType: ImageType
    
    @State var placeHolderImage: String?
    @State private var item: PhotosPickerItem?
    @State private var image: Image?
    @State private var isUploadingImage: Bool = false
    
    private func getImage(by photoPickerItem: PhotosPickerItem?) async {

        // ERROR HERE: Sending 'photoPickerItem.some' risks causing data races
        
        if let data = try? await photoPickerItem?.loadTransferable(type: Data.self) {
            if let uiImage = UIImage(data: data) {
                let croppedImage = uiImage.cropToBounds(width: 200, height: 200)
                
                await uploadImage(uiImage: croppedImage)
            }
        }
    }
    
    private func uploadImage(uiImage: UIImage) async {
        // Upload Code ....
    }
    
    var body: some View {
        PhotosPicker(selection: $item, matching: .images) {
            editableImage()
                .opacity(isUploadingImage ? 0.3 : 1)
                .overlay {
                    if !isUploadingImage {
                        // TODO
                    } else {
                        ProgressView()
                    }
                }
                .clipShape(Circle())
        }
        .disabled(isUploadingImage)
        .onChange(of: item) {
            Task {
                await getImage(by: item)
            }
        }
    }

i tried

@Sendable private func getImage(by photoPickerItem: PhotosPickerItem?) async { // Code }

i watched apple sessions for migration to swift6 but nothing is working


Solution

  • I guess that the best bet is to silence the warning until Apple makes it sendable. For instance, I use this approach to silence the warning even if it's retroactive and then trigger a warning once I migrate to Swift 6.

    #if swift(>=6.0)
    #warning("Revisit @unchecked Sendable on Swift 6 version, PhotosPickerItem should be Sendable by now.")
    #endif
    extension PhotosPickerItem: @unchecked Sendable {}
    

    Or perhaps better solution is to use unsafe nonisloated local varible like this:

    nonisolated(unsafe) let unsafeValue = photoPickerItem