If NSImage is not Sendable, how should async functions return images?

I saw that the macOS 14 SDK added this:

@available(*, unavailable)
extension NSImage : @unchecked Sendable {
}

Do I interpret this correctly to mean that NSImage is not Sendable and this extension’s sole purpose is to document this fact?

I understand why NSImage isn’t safe to use from multiple threads at the same time. But what is the best way to pass around images in async contexts, then? CGImage is Sendable and is a fine substitute for pixel-based images, but what about PDFs? Is the only feasible way to create a PDF-based NSImage by doing everything on the main thread? (Except maybe load the file into a Data?)

On the other hand, UIImage is Sendable, but I suppose that is because it uses a CGImage under the hood?

Post not yet marked as solved Up vote post of marco.masser Down vote post of marco.masser
1.2k views

Replies

No, it means that NSImage is Sendable, but that its conformance cannot be checked by the compiler on purely formal grounds. Quoting from The Swift Programming Language for unchecked:

Apply this attribute to a protocol type as part of a type declaration’s list of adopted protocols to turn off enforcement of that protocol’s requirements.

and Sendable:

Structures that have nonsendable stored properties and enumerations that have nonsendable associated values can be marked as @unchecked Sendable, disabling compile-time correctness checks, after you manually verify that they satisfy the Sendable protocol’s semantic requirements.

@Polyphonic: Thanks for the reply. I understand what @unchecked Sendable means and I agree that this would mean that NSImage is Sendable – but the thing that prompted this question was that the extension is marked as unavailable on all platforms and versions (because of the *). So what does it mean when a class conforms to a protocol in an unavailable extension?

The following experiments use “Complete” for SWIFT_STRICT_CONCURRENCY:

Xcode 14, macOS 13 SDK, macOS 13 Deployment Target

I tried returning an NSImage from an async top-level method and that gives me this warning:


Xcode 15, macOS 14 SDK, macOS 14 Deployment Target

The same code compiles without the hint at the import Cocoa line, which I would interpret to mean that the macOS 14 SDK has been fully audited for Sendability and suppressing the warnings isn’t an option anymore (not that I would want to do that):

Just to make sure, I also tried it with an actor:


So the compiler definitely does know that NSImage is not Sendable, which means the availability annotation in that extension I originally mentioned does not add Sendability conformance but instead seems to document the opposite. This can also be verified by adding the following (unwise) extension to the code in the screenshots, which “fixes” the warning:

extension NSImage: @unchecked Sendable {}

To pass Swift Concurrency-friendly images I recommend to use Image from SwiftUI (which can be simply converted from and to NSImage/UIImage) or even the image raw Data

  • That is a possible solution if all you need is a SwiftUI Image as a SwiftUI view, but that won’t help if you actually want to do anything with the image. AFAICT, there’s no way to get to an image’s data from a SwiftUI Image. I guess this is also the reason why it can be marked as Sendable: SwiftUI guarantees that the properties that are not thread safe will only ever be accessed on the main thread and since SwiftUI is the only thing that has access to that, it can ensure that.

Add a Comment