Calling the selector of a static or class function fails in Swift 4.2

Hello,


somehow today I stumbled upon a strange behaviour in Swift.

I am using an extension for a UIViewController which contains a class function:


extension SettingsViewController {

    @objc class func shakeSwitchChanged(switchState: UISwitch) {
        let userDefaults = UserDefaults.standard
        userDefaults.set(switchState.isOn, forKey: PokerUserDefaultKeys.FlipOnShake.rawValue)
    }

}


In the view I am using a UISwitch and add a target to it in this way:


flipSwitch?.addTarget(self, action: #selector(SettingsViewController.shakeSwitchChanged(switchState:)), for: .valueChanged)


In Xcode 10 this code comiles fine (no errors, no warnings), but during runtime the app crashes when flipping the switch by saying:


2018-10-12 12:58:53.649412+0200 bridgingIT Planning Poker[30655:4166508] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[bridgingIT_Planning_Poker.SettingsViewController shakeSwitchChangedWithSwitchState:]: unrecognized selector sent to instance 0x7fdb48e14b00'


Putting the function into the class itself, does not work either.


When i change the function definition to be an instance function (i.e. removing "class") everything works fine.

It is also very strange that if I define the function as an instance function of the class, everything works although the selector remains the same (#selector(SettingsViewController.shakeSwitchChanged(switchState:)). I expected at least compile-time errors or warnings, but nothing. Even the app runs fine with this...


Am I doing something wrong or is it possibly a bug in Swift 4.2?



Thanks in advance and best regards

Martin

In Objective-C, selector is just an encoded method name.

So, the notation `#selector(SettingsViewController.shakeSwitchChanged(switchState:))` just generates a selector instance representing the method name `shakeSwitchChangedWithSwitchState:`. it does not contain any info about its class.


And if you set target in this way:

flipSwitch?.addTarget(self, action: #selector(SettingsViewController.shakeSwitchChanged(switchState:)), for: .valueChanged)

iOS sends the selector to `self` with some parameters on the event specified.

So, `self` needs to be able to respond to the selector, I believe `self` is an instance, not a class.


Am I doing something wrong or is it possibly a bug in Swift 4.2?


You are doing things wrong, not a bug. When you specify `self` as an action target, `action` needs to be an instance method of `self`.


I expected at least compile-time errors or warnings

It is difficult for compilers to detect some inconsistency between two separate parameters. (Not impossible, but a big effort.)


Until some more Swifty and safe way of handling events is introduced in the future, you should better use self-leaded selector when you specify `self` as a target:

flipSwitch?.addTarget(self, action: #selector(self.shakeSwitchChanged(switchState:)), for: .valueChanged)

I agree with everything that OOPer wrote but I want to add one additional item: If you do want to create an action class method, it is possible. Here’s the syntax to set up the action (I was using a bar button item for testing, but it’s the same for any target/action pair):

let b = UIBarButtonItem(
    barButtonSystemItem: .action, 
    target: type(of: self), 
    action: #selector(MainViewController.testAction(_:))
)

And here’s the class method itself:

@objc
class func testAction(_ sender: Any) {
    NSLog("testAction(_:)")
}

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
Calling the selector of a static or class function fails in Swift 4.2
 
 
Q