Crash on async function calls

Hello everyone!

I stumbled upon an async issues that seem too straightforward to actually be true.

The problems

Async IBAction crash

If you mark an @IBAction as async, the app will crash:

@IBAction func ibActionButtonPressed() async {
    // Crash when called
}

The exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[AsyncIBActionCrash.ViewController ibActionButtonPressed]: unrecognized selector sent to instance 0x7fae38e0c180'

Async notification crash

If a notification is called that is linked to a selector which is marked as async, the app will crash:

func setup() {
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(asyncFunction),
        name: Notification.Name("AsyncNotification"),
        object: nil
    )
}

func callNotification() {
    NotificationCenter.default.post(name: Notification.Name("AsyncNotification"), object: nil)
}

@objc func asyncFunction() async {
    // Crash when called from notification
}

The exception:
EXC_BAD_ACCESS (code=1, address=0x8)

performSelector crash

Lastly, the app also crashes if a performSelector is called on a selector that is marked as async:

func callPerformSelector() {
    performSelector(onMainThread: #selector(asyncFunction), with: nil, waitUntilDone: false)
}

@objc func asyncFunction() async {
    // Crash when called from performSelector
}

The exception:
EXC_BAD_ACCESS (code=1, address=0x10)

The environment

  • Xcode 13.4.1
    • Swift 5.6.1
  • iOS SDK 15.5
  • Crashes happen on both simulator and on a physical device

Discussion

The workaround for these are quite easy – one must provide a wrapper function so that the @IBAction, notification or performSelector do not call the async function directly. E.g., with @IBAction crash:

@IBAction func ibActionButtonPressed() {
    Task {
        await asyncFunction()
    }
}

func asyncFunction() async {
    // No crash
}

However, the main problem is probably even not that we cannot make these functions async but that nothing complains about it – analyzer is silent, compiler is silent – the issue is only discovered on runtime (and in the case of notification or performSelector it is difficult to understand why it even crashed).

I have created a simple project with the issue: GitHub - kkizlaitis/AsyncCrashes: A simple project with multiple async crash issues

Yep, it’s due to an implicitly required completion handler needed for the selector to work correctly. I’ve reported it and think it should diagnosed but I don’t know if we’ll see a fix. Performing Selector Marked async Crashes · Issue #60084 · apple/swift · GitHub

1 Like

Thanks for linking this issue! I have not stumbled upon it previously. My post, however, includes not only performSelector (which is less used) but also just simple @IBAction markings and bindings with XIBs that are still supported by Xcode and are used widely in non-SwiftUI projects.

Although it could be that the @IBAction and NotificationCenter both use performSelector under the hood? I found three different crashes but maybe it's the same issue?

With regards to @objc async, I guess I was the first to document this in October last year. There's even a forum thread about it which received a tiny bit of attention from the core team, but then ­– as with oh so many bugs ­– faded unresolvedly into oblivion.

Yes, those are all the same issue, as anything that captures a selector will want to perform it sometime in the future. Now, if Apple wants to fix every performSelector in their codebases to properly handle async selectors, that's fine, but seems unlikely. It seems like something should be fixed but it's not obvious where that fix should be.

@objc async methods as a general rule are supported; they take an implicit completion handler block argument. Like everything else in ObjC, it's incumbent on you to make sure you call them correctly. I've sometimes dreamed of having a type-checked selector feature in ObjC, which we'd be able to take advantage of it in Swift, but it's a major undertaking, not least because people are in practice very lax about actually calling ObjC methods using the right signature.

@IBAction async methods specifically, however, can simply be rejected outright by the compiler. We should be able to close that hole soon. There are ways I can imagine supporting them, but they'll need some significant investment.

1 Like

Looks like we already disallow @IBAction async, but it seems it hasn’t been cherry-picked to 5.7.

2 Likes

What does that mean in the context of [SR-15291] Concurrency: Crash when invoking async @objc function · Issue #57613 · apple/swift · GitHub? How can I ensure that NotificationCenter is calling this correctly?

NotificationCenter is quite clear about this:

The method that aSelector specifies must have one and only one argument (an instance of NSNotification ).

That is not compatible with a completion handler, and so you cannot use the selector of an async method with it, just like you cannot use the selector of a method with two arguments, or one that takes a String.