SE-0463: Import Objective-C completion handler parameters as `@Sendable`

Hello, Swift community.

The review of SE-0463: Import Objective-C completion handler parameters as @Sendable begins now and runs through March 10th, 2025.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager via email or the forum messaging feature. When contacting the review manager directly, please put [SE-0463] at the start of the subject line.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available here.

Thank you,

John McCall
Review Manager

9 Likes

Nice & easy, yes please.

Absolutely, yes. I can't think of a reason not to do this by default. However, there is a small subset of Objective-C API on Apple's platforms that assumes the completion handler will run on a specific dispatch queue (not interesting to Swift concurrency) or on the main thread/actor/queue (should be surfaced as @MainActor rather than @Sendable.)

If a completion handler is explicitly annotated __attribute__((__swift_attr__("@MainActor"))) or equivalent, will that suppress @Sendable? Or is it moot in the scenarios where the compiler's behaviour is going to change? (This is distinct from "completion handlers of global actor isolated functions" in that the function may not be actor-isolated, but its completion handler is.)

@MainActor on a function type implies @Sendable. (It didn't originally, but this was fixed in SE-0434.)

2 Likes

Indeed it does! I completely blanked on it. Thanks!

Yes, definite +1. If the objc interface does not make any promise, the only import that makes sense is @Sendable.

a big +1 for me.

What will be the impact on users of the Core Data API?

Core Data requires NSManagedObjectContext (non-Sendable ) to be accessed across the perform closure.

Xcode makes it impossible to opt out of SWIFT_STRICT_CONCURRENCY (at least minimal is required).
I am therefore concerned that this proposal will generate the inevitable warning.

This is addressed in source compatibility section of the proposal:

This change has no effect in language modes prior to Swift 6 when using minimal concurrency checking

All Sendable warnings are suppressed under minimal concurrency checking. Warnings will only be added in complete concurrency checking or the Swift 6 language mode.

But wouldn’t that still mean that whenever a project uses the swift 6 language mode with complete concurrency checking I‘d be left with a inevitable warning in the aforementioned example?

+1 Having all my obj-c completion handlers annotated with NS_SWIFT_SENDABLE, this seems like a very sensible thing to do.

All Sendable warnings are suppressed under minimal concurrency checking.

Thanks. It seems that I misunderstood the minimal check due to a bug in Xcode 16.0.

I have code that accesses NSPredicate across a DispatchQueue closure, which results in a non-Sendable warning being reported in Xcode 16.0, but suppressed in Xcode 16.2.

+1

I just had to debug a mysterious crash because of this today, and I'm delighted that there's already a proposal in review to address it.

The issue I encountered was that AVFoundation's AVAudioPlayerNode allows scheduling buffers of audio to be played, and these scheduling APIs offer a completion handler that allows you to be notified at various stages of the audio rendering and playback process. The callback is invoked on an arbitrary thread.

If you call this from an actor-isolated context, the compiler assumes the callback will be invoked on the original actor.

@MainActor
final class Player {
  let node: AVAudioPlayerNode
  var state = 0

  func play() {
    node.schedule(..., completionHandler: { _ in
      self.state += 1 // Compiler just allows this.
    })
  }
}

Even though the compiler allows it, with no warnings, it's invalid and crashes at runtime. I managed to debug what was happening and figured out for myself that writing the closure @Sendable _ in allowed me to work around it, but it's not entirely obvious and we shouldn't expect every developer to do that.

What's more, even if you try to work around it by only modifying your isolated state from the correct context (e.g. launching a detached Task from the completion handler), that still isn't enough to work around the issue, because the crash occurs in the compiler-generated preamble of the completion handler itself.

3 Likes

Yeah, I think this is a problem too, and the compiler should elide the dynamic check if the closure does not actually access isolated state: Elide dynamic actor isolation check when closure does not access actor-isolated state. · Issue #75856 · swiftlang/swift · GitHub

2 Likes