[Pitch] Custom Main and Global Executors

@stackotter and I have come across an example where hooking into MainActor dynamically would be useful.

When using a GUI framework like GTK, the main thread is never serviced and @MainActor functions never run. Instead, you'd need a separate actor (let's call it @UIActor) to coordinate scheduling work with GTK so that it always runs with exclusive isolation with UI updates. This is annoying, but possible; I have successfully implemented such a UIActor.

However, we can't mark SwiftCrossUI's View protocol as @UIActor, because that causes problems in other backends -- particularly in UIKitBackend's UIViewRepresentable, since UIView is @MainActor. There's no way (as far as I'm aware) to tell the compiler that @MainActor and @UIActor are the same thing when using the UIKitBackend, and even if we used assumeIsolated in UIViewRepresentable's implementation, any user who wants to use any other UIKit API (e.g. calling a method on UIApplication.shared inside a button callback) will run into the same issue.

Additionally, I believe this is technically possible on macOS, but I'm not sure why one would do this:

import SwiftCrossUI
import AppKitBackend
import GtkBackend

struct MyGtkApp: App {
  let backend = GtkBackend()

  var body: some Scene { ... }
}

struct MyAppKitApp: App {
  let backend = AppKitBackend()

  var body: some Scene { ... }
}

public func main() {
  if someDynamicRuntimeCondition() {
    MyGtkApp.main()
  } else {
    MyAppKitApp.main()
  }
}

In which case whether @MainActor works out-of-the-box or not is decided dynamically at runtime.

3 Likes