CIImage: Incorrect Sendable Conformance?

Context:

The docs for CIImage explicitly declare it safe to use across threads:

CIContext and CIImage objects are immutable, which means each can be shared safely among threads. Multiple threads can use the same GPU or CPU CIContext object to render CIImage objects.

I'm using a CIImage to generate a checkerboard background. I do the work once:

actor Foo
{
    private let checkerboardImage: CIImage

    init()
    {
        let color0 = CIColor(red: 82/255, green: 82/255, blue: 82/255, alpha: 1.0)
        let color1 = CIColor(red: 30/255, green: 32/255, blue: 34/255, alpha: 1.0)
        
        guard let checkerboardFilter: CIFilter = CIFilter(name: "CICheckerboardGenerator", parameters: ["inputColor0": color0, "inputColor1": color1, kCIInputCenterKey: CIVector(x: 0, y: 0), kCIInputWidthKey: 50.0]),
              let image = checkerboardFilter.outputImage else
        {
            fatalError("Failed to generate the checkerboard CIImage in \(#file)")
        }
        
        checkerboardImage = image
    }
}

Then, I use this image in a nonisolated method:

nonisolated func doWork()
{
    if let checkerboardCGImage: CGImage = CIContext().createCGImage(checkerboardImage, from: CGRect(x: 0, y: 0, width: 400, height: 400))
    {
        // Composite another image on top of the checkerboard background image.
    }
}

This produces a warning:

Non-sendable type 'CIImage' in asynchronous access to actor-isolated property 'checkerboardImage' cannot cross actor boundary

Question:

This is just a spurious warning because Apple failed to audit its frameworks for Sendable conformance, correct? CIImage is explicitly listed as thread-safe and I'm not passing the CIFilter (which is explicitly listed as NOT thread-safe).

Adding @preconcurrency to the Core Image import does nothing (I have yet to find a single case where @preconcurrency actually does something; it seems like Apple just forgot to implement it entirely.)

Essentially, yes. Apple's various frameworks are in various states of correctness, but most that I've seen used the general audit tool NS_HEADER_AUDIT_BEGIN(nullability, sendability) to explicitly mark the headers as non sendable, then never got around to marking the actually Sendable stuff NS_SWIFT_SENDABLE. CIImage doesn't even do that, so it's likely that Obj-C types imported into Swift are nonSendable by default (I don't recall whether that was part of a proposal) and require the aforementioned explicit annotation to be visibly Sendable. Hopefully we see this audit complete this summer for Swift 6, as there are still a lot of Apple APIs that are safe but nonSendable, like FileManager and UserDefaults.

5 Likes

Hmm, in a single-file Xcode (15.3) project that includes just your code, with complete concurrency checking enabled I can reproduce the warning with import CoreImage and lose the warning when I add @preconcurrency.

1 Like

Hmm. I'm also on Xcode 15.3, but the project has a few hundred files (it's a large Mac app). I've never had any luck with @preconcurrency and in this case I'm staring right at it while the warnings persist. Who knows?

Just a guess, possibly this? (Preconcurrency ignored if module already transitively included)

That may be the cause. There's only one @import CoreImage written directly by me and that's decorated with @preconcurrency. But the project does use lots of other frameworks and it's quite possible one of those includes Core Image.

Still, I'd consider this a bug. The whole point of @preconcurrency is: "Don't show me any concurrency warnings for APIs in this module I'm importing in this file I'm currently working on"

That's the goal of the decorator. If it doesn't do that, it's broken—no matter what complex excuses compiler engineers come up with!

1 Like