Repository: flutter
Issue: [engine][macOS/iOS] Make Flutter's Public Headers Swift 6 Friendly
Labels: platform-ios,engine,platform-mac,c: proposal,P2,team-ios,triaged-ios

### Use case

With the release of Swift 6, the Swift community is adopting **Swift Strict Concurrency** and the **Swift 6 Language Mode**. However, based on my testing, Flutter's public headers are not fully compatible with Swift 6.

For instance, the following code generates warnings when used in **Swift Strict Concurrency** (`-swift-version 5 -enable-upcoming-feature StrictConcurrency`) and errors in **Swift 6 Language Mode** (`-swift-version 6`):

```swift
@MainActor
class StringChannel {
    static let shared = StringChannel()

    static func setUp(
        binaryMessenger: FlutterBinaryMessenger,
        codec: NSObjectProtocol & FlutterMessageCodec
    ) {
        let channel = FlutterBasicMessageChannel(
            name: "com.example.TestApp/StringChannel",
            binaryMessenger: binaryMessenger,
            codec: codec
        )

        channel.setMessageHandler { (message, reply) in
            print("native: StringChannel received message: \(message ?? "nil")")
            DispatchQueue.global().asyncAfter(deadline: .now() + 1) { @Sendable in
                let message = "StringChannel reply message"
                print("native: StringChannel sending reply: \(message)")
                reply(message)  // <-- Warning: Capture of 'reply' with non-sendable type 'FlutterReply' in a `@Sendable` closure
                
                Task { @MainActor in
                    let message = "StringChannel message from background"
                    print("native: StringChannel sending message: \(message)")
                    // There are two versions of sendMessage in FlutterChannel.h. One does not 
                    // have a reply callback, while the other does. It seems that if a Swift 
                    // function is async or in an isolated context, Swift always chooses the 
                    // version with a reply callback and converts it to an async function. 
                    // That's why we need to await the result of sendMessage.
                    let reply = await channel.sendMessage(message)  
                    // <-- Warning: Capture of 'channel' with non-sendable type 'FlutterBasicMessageChannel' in a `@Sendable` closure
                    // <-- Warning: Non-sendable type 'Any?' returned by implicitly asynchronous call cannot cross actor boundary; this is an error in Swift 6 Language Mode
                    print("native: StringChannel received reply: \(reply ?? "nil")")
                }
            }
        }
    }
}
```

Currently, the workaround is to use the `@preconcurrency` attribute when importing the Flutter module in Swift. However, this is neither ideal nor sustainable, as the Swift compiler bypasses all concurrency checks for the Flutter module.

### Proposal

To address this issue, I propose adding proper Swift concurrency annotations to Flutter's public headers. For example:

```objc
#if __has_attribute(swift_attr)
#define FLUTTER_SWIFT_SENDABLE __attribute__((swift_attr("@Sendable")))
#define FLUTTER_SWIFT_MAIN_ACTOR __attribute__((swift_attr("@MainActor")))
#else
#define FLUTTER_SWIFT_SENDABLE
#define FLUTTER_SWIFT_MAIN_ACTOR
#endif

typedef void (^FLUTTER_SWIFT_SENDABLE FlutterReply)(FLUTTER_SWIFT_SENDABLE id _Nullable reply);

FLUTTER_DARWIN_EXPORT
FLUTTER_SWIFT_MAIN_ACTOR
@interface FlutterBasicMessageChannel : NSObject
...
@end
```

This approach would:

1. Make `FlutterReply` sendable, enabling its use in Swift concurrency contexts without warnings.
2. Isolate `FlutterBasicMessageChannel` to the main actor, similar to `@UiThread` annotations used in Android (`flutter/shell/platform/android/io/flutter/plugin/common/BinaryMessenger.java`).

The `swift_attr` attribute is supported by Clang and ignored in non-Swift contexts, ensuring no runtime behavior changes.

Apple provides a [Swift 6 Concurrency Migration Guide](https://www.swift.org/migration/documentation/migrationguide), which includes guidelines for Objective-C APIs. These resources offer a solid foundation for making Flutter's public headers Swift 6 compatible.


To ensure compatibility and prevent regressions:

1. **Add Swift Test Code**: Include test cases in the Flutter repository that use Flutter's public headers to verify concurrency compliance.
2. **Enable GN Build System for Swift**: The existing GN build system supports Swift. While some helper scripts (available in the Chromium repository) might be needed, I have successfully set this up locally and found it straightforward.
3. **CI Validation**: Enable warnings as errors for Swift, ensuring compatibility with Swift 6 in CI pipelines.


(Based on feedback, I can create a detailed design document outlining the implementation in greater depth.)