|
| 1 | +--- |
| 2 | +title: 'Access MainActor Synchronously Safely' |
| 3 | +author: 'Clive Liu' |
| 4 | +layout: post |
| 5 | +tags: [Swift, Concurrency] |
| 6 | +--- |
| 7 | + |
| 8 | +> **Use with caution:** Avoid accessing `MainActor` synchronously unless absolutely necessary. |
| 9 | +{: .prompt-warning } |
| 10 | + |
| 11 | +In rare cases—particularly when integrating with legacy code—you may need to synchronously access `MainActor`-isolated code. While this approach should be avoided when possible, Swift does provide a safe mechanism for doing so when absolutely necessary. |
| 12 | + |
| 13 | +Here's how you can accomplish that: |
| 14 | + |
| 15 | +```swift |
| 16 | +extension MainActor { |
| 17 | + |
| 18 | + /// Executes a closure synchronously on the `MainActor`. |
| 19 | + /// |
| 20 | + /// This method allows synchronous access to `MainActor`-isolated code. |
| 21 | + /// |
| 22 | + /// - Parameter operation: A closure that runs on the `MainActor`. |
| 23 | + /// - Returns: The value returned by `operation`. |
| 24 | + /// - Throws: Rethrows any error thrown by `operation`. |
| 25 | + /// |
| 26 | + /// - Warning: Synchronous access to actors should be avoided when possible. |
| 27 | + /// This is intended for exceptional cases, such as bridging with legacy APIs. |
| 28 | + public static func sync<R: Sendable>(operation: @MainActor () throws -> R) rethrows -> R { |
| 29 | + if Thread.isMainThread { |
| 30 | + // This is valid as per SE-0424: |
| 31 | + // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0424-custom-isolation-checking-for-serialexecutor.md |
| 32 | + // |
| 33 | + // Being able to assert isolation for non-task code this way is important enough |
| 34 | + // that the Swift runtime actually already has a special case for it: |
| 35 | + // even if the current thread is not running a task, isolation checking will succeed |
| 36 | + // if the target actor is the MainActor and the current thread is the main thread. |
| 37 | + try MainActor.assumeIsolated(operation) |
| 38 | + } else { |
| 39 | + // As per https://developer.apple.com/documentation/swift/mainactor |
| 40 | + // MainActor is a singleton actor whose executor is equivalent to the main dispatch queue. |
| 41 | + // The compiler would also yell at you if you use non-main queue. |
| 42 | + // Therefore, this code is valid. |
| 43 | + // |
| 44 | + // - Note: You cannot do `DispatchQueue.main.sync` if you're already on main thread, |
| 45 | + // because that will cause a deadlock. Hence, the `isMainThread` check. |
| 46 | + try DispatchQueue.main.sync(execute: operation) |
| 47 | + } |
| 48 | + } |
| 49 | +} |
| 50 | +``` |
| 51 | + |
0 commit comments