24

I am trying to add an async do/catch/defer action to a UIButton. However, if I just call a method in the defer block, I get Call to main actor-isolated instance method XXX in a synchronous nonisolated context error. The workaround I found is to wrap it in another Task block, like the following. Just want to check if this is the correct way to do it? It'd be good if someone can explain what that error message actually says.

@objc private func post(_ sender: UIButton) {
    Task {
        // defer { dismiss(animated: true) } -- Doesn't work
        defer { Task { await dismiss(animated: true) } }
        do {
            try await doSomethingAsync()
        } catch {
            print(error)
        }
    }
}
George
  • 25,988
  • 10
  • 79
  • 133
Jack Guo
  • 3,959
  • 8
  • 39
  • 60
  • 1
    Actor methods are asynchronous. They are asynchronous because you don't know when they will be run - to prevent multiple methods simultaneously being run on the actor. So you are basically trying to run an asynchronous method in a synchronous context, which you can't do. It's almost like when calling an `async` method in a normal function _not_ marked `async`. – George Dec 13 '21 at 10:35
  • 3
    The other work-around is to not `defer`. If you want it to happen if only successful, then put it after the `doSomethingAsync` call. If you want it to happen whether successful or not, put it after the `do`-`catch` block. – Rob Dec 21 '21 at 15:50

1 Answers1

15

This is bug in current swift versions as compiler is not able to recognize the global actor context for a defer block, the discussion for this is going on swift forum and a PR with fix also available that should resolve this issue in future swift versions. For now, explicitly global actor context need to be provided for code to compile:

defer { Task { @MainActor in dismiss(animated: true) } }
Soumya Mahunt
  • 2,148
  • 12
  • 30