How can I deal with @objc inference deprecation with #selector() in Swift 4?

SwiftXcodeSwift4

Swift Problem Overview


I'm trying to convert my project's source code from Swift 3 to Swift 4. One warning Xcode is giving me is about my selectors.

For instance, I add a target to a button using a regular selector like this:

button.addTarget(self, action: #selector(self.myAction), for: .touchUpInside)

This is the warning it shows:

>Argument of '#selector' refers to instance method 'myAction()' in 'ViewController' that depends on '@objc' attribute inference deprecated in Swift 4 > > Add '@objc' to expose this instance method to Objective-C

Now, hitting Fix on the error message does this to my function:

// before
func myAction() { /* ... */ }

// after
@objc func myAction() { /* ... */ }

I don't really want to rename all of my functions to include the @objc mark and I'm assuming that's not necessary.

How do I rewrite the selector to deal with the deprecation?


Related question:

Swift Solutions


Solution 1 - Swift

The fix-it is correct – there's nothing about the selector you can change in order to make the method it refers to exposed to Objective-C.

The whole reason for this warning in the first place is the result of SE-0160. Prior to Swift 4, internal or higher Objective-C compatible members of NSObject inheriting classes were inferred to be @objc and therefore exposed to Objective-C, therefore allowing them to be called using selectors (as the Obj-C runtime is required in order to lookup the method implementation for a given selector).

However in Swift 4, this is no longer the case. Only very specific declarations are now inferred to be @objc, for example, overrides of @objc methods, implementations of @objc protocol requirements and declarations with attributes that imply @objc, such as @IBOutlet.

The motivation behind this, as detailed in the above linked proposal, is firstly to prevent method overloads in NSObject inheriting classes from colliding with each other due to having identical selectors. Secondly, it helps reduce the binary size by not having to generate thunks for members that don't need to be exposed to Obj-C, and thirdly improves the speed of dynamic linking.

If you want to expose a member to Obj-C, you need to mark it as @objc, for example:

class ViewController: UIViewController {



@IBOutlet weak var button: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()
    button.addTarget(self, action: #selector(foo), for: .touchUpInside)
}

<b>@objc</b> func foo() {
   // ... 
}




}

}

(the migrator should do this automatically for you with selectors when running with the "minimise inference" option selected)

To expose a group of members to Obj-C, you can use an @objc extension:

@objc extension ViewController {
    
    // both exposed to Obj-C
    func foo() {}
    func bar() {}
}

This will expose all the members defined in it to Obj-C, and give an error on any members that cannot be exposed to Obj-C (unless explicitly marked as @nonobjc).

If you have a class where you need all Obj-C compatible members to be exposed to Obj-C, you can mark the class as @objcMembers:

@objcMembers
class ViewController: UIViewController {
   // ...
}

Now, all members that can be inferred to be @objc will be. However, I wouldn't advise doing this unless you really need all members exposed to Obj-C, given the above mentioned downsides of having members unnecessarily exposed.

Solution 2 - Swift

As Apple Official Documentation. you need to use @objc to call your Selector Method.

> In Objective-C, a selector is a type that refers to the name of an > Objective-C method. In Swift, Objective-C selectors are represented by > the Selector structure, and can be constructed using the #selector > expression. To create a selector for a method that can be called from > Objective-C, pass the name of the method, such as > #selector(MyViewController.tappedButton(sender:)). To construct a selector for a property’s Objective-C getter or setter method, pass > the property name prefixed by the getter: or setter: label, such as > #selector(getter: MyViewController.myButton).

Solution 3 - Swift

As of, I think Swift 4.2, all you need to do is assign @IBAction to your method and avoid the @objc annotation.

let tap  =  UITapGestureRecognizer(target: self, action: #selector(self.cancel))

@IBAction func cancel()
{
    self.dismiss(animated: true, completion: nil)
}

Solution 4 - Swift

As already mentioned in other answers, there is no way to avoid the @objc annotation for selectors.

But warning mentioned in the OP can be silenced by taking following steps:

  1. Go to Build Settings
  2. Search for keyword @objc
  3. Set the value of Swift 3 @objc interface to Off

below is the screenshot that illustrates the above mentioned steps:

Silencing the warning

Hope this helps

Solution 5 - Swift

If you need objective c members in your view controller just add @objcMembers at the top of the view controller. And you can avoid this by adding IBAction in your code.

@IBAction func buttonAction() {

}

Make sure to connect this outlet in storyboard.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLinusGeffarthView Question on Stackoverflow
Solution 1 - SwiftHamishView Answer on Stackoverflow
Solution 2 - SwiftKiran SarvaiyaView Answer on Stackoverflow
Solution 3 - SwiftLogan SeaseView Answer on Stackoverflow
Solution 4 - SwiftS1LENT WARRIORView Answer on Stackoverflow
Solution 5 - SwiftRenjish CView Answer on Stackoverflow