Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 5.5: Async @objc didPullToRefresh selector crashes app with error EXC_BAD_ACCESS

I have a table to which I've added a refreshControl and when I pull down to refresh the content, I reset the array that feeds the table with data and then immediately request new data through an API call.

Until now, I have used completion handlers and protocols to get the data into the table view but I want to move the logic to async/await because of the complexity needed by the network calls and the pyramid of nested closures.

Populating the view in viewDidLoad works fine but with pullToRefresh selector I get an error:

Thread 1: EXC_BAD_ACCESS (code=1, address=0xbcf917df8160)

Implementation:

override func viewDidLoad() {
    super.viewDidLoad()
    setupView()
    setupTableView()
    setupTableRefreshControl()
    Task {
      await getBalances() //async network call
      myTable.reloadData()
    }
  } 
func setupTableRefreshControl() {
    myTable.refreshControl = UIRefreshControl()
    myTable.refreshControl?.addTarget(self, action: #selector(didPullToRefresh), for: .valueChanged)
  }

Code that crashes app:

   @objc func didPullToRefresh() async {
    balance.reset() // reset array to []
    Task {
      await getBalances() //async network call
      myTable.reloadData()
    }
  }
like image 253
cvld Avatar asked Oct 15 '25 19:10

cvld


1 Answers

At the time of writing, @objc selectors and async methods don't play well together and can result in runtime crashes, instead of an error at compile-time.

Here's a sample of how easy it is to inadvertently replicate this issue while converting our code to async/await: we mark the following method as async

@objc func myFunction() async {
    //...

not noticing that it is also marked as @objc and used as a selector

NotificationCenter.default.addObserver(
    self,
    selector: #selector(myFunction),
    name: "myNotification",
    object: nil
)

while somewhere else, a notification is posted

NotificationCenter.default.post(name: "myNotification", object: nil)

Boom 💥 EXC_BAD_ACCESS

Instead, we should provide a wrapper selector for our brand new async method

@objc
func myFunctionSelector() {
    Task {
        await myFunction()
    }
}

func myFunction() async { 
    //... 

and use it for the selector

NotificationCenter.default.addObserver(
    self,
    selector: #selector(myFunctionSelector),
    name: "myNotification",
    object: nil
)
like image 169
ACLima Avatar answered Oct 17 '25 07:10

ACLima