Home > database >  Interoperating Async/await, @MainActor and DispatchQueue.main.async
Interoperating Async/await, @MainActor and DispatchQueue.main.async

Time:04-02

Say I have this code:

class Presenter {
    var viewToUpdate: UIView!
    
    func updateUI() {
        viewToUpdate.backgroundColor = .red
    }
}

class ShinyNewAsyncAwaitClass {
    func doAsyncAwaitThing() async {
        // make network call or something
    }
}

class OtherClassThatICantUpdateToAsyncAwaitYet {
    func doOldClosureBasedThing(completion: @escaping () -> Void) {
        // make network call or something
        completion()
    }
}

class TheClassThatUsesAllThisStuff {
    var newClass: ShinyNewAsyncAwaitClass!
    var oldClass: OtherClassThatICantUpdateToAsyncAwaitYet!
    var presenter: Presenter!
    
    func doSomethingWithNewClass() {
        Task {
            await self.newClass.doAsyncAwaitThing()
            
            // ---->>> What do I do here? <<<<----
            await self.presenter.updateUI()
        }
    }
    
    func doSomethingWithOldClass() {
        oldClass.doOldClosureBasedThing {
            DispatchQueue.main.async {
                self.presenter.updateUI()
            }
        }
    }
    
    func justUpdateTheView() {
        self.presenter.updateUI()
    }
}

In short, I have three classes. One I can update to async/await, the other I can't, and one that uses both. Both need access to a function that updates UI, both will need to access that function on the main thread.

I saw somewhere I can add @MainActor to the updateUI function, but in cases where I'm already on the main thread and I just want to call updateUI, like in justUpdateTheView I get this error:

Call to main actor-isolated instance method 'updateUI()' in a synchronous nonisolated context

Add '@MainActor' to make instance method 'justUpdateTheView()' part of global actor 'MainActor'

I can't define justUpdateTheView as @MainActor, because we're trying to update our project to the new concurrency stuff slowly and this would cause a chain reaction of changes that need to be made.

What to do for the best? Can I do something like this:

func doSomethingWithNewClass() {
    Task {
        await self.newClass.doAsyncAwaitThing()
        
        DispatchQueue.main.async {
            self.presenter.updateUI()
        }
    }
}

It compiles, but are there any gotchas to be aware of?

CodePudding user response:

You can do something like this to run the UI code on the MainActor:

func doSomethingWithNewClass() {
    Task {
        await self.newClass.doAsyncAwaitThing()
        
        await MainActor.run {
            self.presenter.updateUI()
        }
    }
}
  • Related