swiftdispatchnsimageswift-concurrency

Capture of variable with non-sendable type NSImage in a '@Sendable closure'


I'm new to swift concurrency, I noticed that my code freezes UI when trying to initialize NSImage, so I decided to do it on the global thread like this:

func chooseImage() {
    Task { @MainActor in
        
        let openPanel = NSOpenPanel()
        openPanel.prompt = "Select File"
        openPanel.allowsMultipleSelection = false
        openPanel.canChooseDirectories = false
        openPanel.canCreateDirectories = false
        openPanel.canChooseFiles = true
        openPanel.allowedContentTypes = [.image]
        openPanel.begin { (result) -> Void in
            if result.rawValue == NSApplication.ModalResponse.OK.rawValue {
                guard let url = openPanel.url else {return}
                self.fileURL = url
                loading = true
                DispatchQueue.global(qos:.userInitiated).async
                {
                    guard let chosenImage = NSImage(contentsOf: url) else { return }
                    DispatchQueue.main.async()
                    {
                        autoreleasepool {
                            self.nsImage = chosenImage
                        }
                        self.recongizeText()
                        loading = false
                    }
                }
   }

Does this mean that NSImage is not thread safe? does this warning really mean that there might be some kind of race condition, and if so what would be the best way to fix this?


Solution

  • As mentioned in the comments mixing GCD and Swift Concurrency is bad practice.

    This is a translation to async/await

    @MainActor
    func chooseImage() async  {
        
        let openPanel = NSOpenPanel()
        openPanel.prompt = "Select File"
        openPanel.canChooseDirectories = false
        openPanel.allowedContentTypes = [.image]
        let result = await openPanel.begin()
        guard result == .OK, let url = openPanel.url else { return }
        self.fileURL = url
        loading = true
        let task = Task.detached {
            try? Data(contentsOf: url)
        }
        guard let data = await task.value, let image = NSImage(data: data) else { return }
        self.nsImage = image
        self.recongizeText() 
        loading = false
    }