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):
@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:
#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:
- Make
FlutterReply sendable, enabling its use in Swift concurrency contexts without warnings.
- 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, 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:
- Add Swift Test Code: Include test cases in the Flutter repository that use Flutter's public headers to verify concurrency compliance.
- 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.
- 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.)
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):Currently, the workaround is to use the
@preconcurrencyattribute 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:
This approach would:
FlutterReplysendable, enabling its use in Swift concurrency contexts without warnings.FlutterBasicMessageChannelto the main actor, similar to@UiThreadannotations used in Android (flutter/shell/platform/android/io/flutter/plugin/common/BinaryMessenger.java).The
swift_attrattribute is supported by Clang and ignored in non-Swift contexts, ensuring no runtime behavior changes.Apple provides a Swift 6 Concurrency Migration Guide, 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:
(Based on feedback, I can create a detailed design document outlining the implementation in greater depth.)