You can kind of hack around it:
actor Foo {
let queue = DispatchSerialQueue(label: "My Foo")
nonisolated var unownedExecutor: UnownedSerialExecutor {
queue.asUnownedSerialExecutor()
}
private nonisolated func assumeIsolatedHack<T>(
_ block: (isolated Foo) throws -> T
) rethrows -> T where T: Sendable {
// Before Swift 6, dispatch didn't work well with `Actor.assumeIsolated`.
// It can report false negatives - where you are actually on the correct queue but 'assumeIsolated'
// doesn't know it. That was fixed in SE-0424:
// https://github.com/swiftlang/swift-evolution/blob/main/proposals/0424-custom-isolation-checking-for-serialexecutor.md
//
// This feature requires a new version of the Swift runtime, so we need to perform an OS version check
// and on older systems we need an ugly hack to emulate the new 'assumeIsolated' for Dispatch.
if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, *) {
return try self.assumeIsolated(block)
} else {
dispatchPrecondition(condition: .onQueue(self.queue))
return try withoutActuallyEscaping(block) {
let isolationStripped = unsafeBitCast($0, to: ((Foo) throws -> T).self)
return try isolationStripped(self)
}
}
}
}
Then you can use actors for Obj-C delegates who always get messages posted on the queue you configure them with.