135

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:

Community
  • 1
  • 1
LinusGeffarth
  • 27,197
  • 29
  • 120
  • 174
  • 3
    Nope, marking them as `@objc` *is* now necessary in order to expose them to Obj-C, and therefore use with selectors. – Hamish Jun 06 '17 at 12:47
  • 3
    So the deprecated part is inferring public-access functions as `@objc`? That is a bit annoying, but I generally make these functions private, requiring me to mark it as `@objc` anyways. – Connor Neville Jun 06 '17 at 12:49
  • 2
    See [SE-0160](https://github.com/apple/swift-evolution/blob/master/proposals/0160-objc-inference.md) for more info about the change. Another alternative is marking your given class as `@objcMembers` in order to expose all Obj-C compatible members to Obj-C, but I wouldn't advise that unless you actually need your entire class to be exposed. – Hamish Jun 06 '17 at 12:50
  • What would be the consequence of that, @Hamish? It seems more convenient to set the whole class to `@objc` than doing that for each function individually. – LinusGeffarth Jun 06 '17 at 12:52
  • 3
    @LinusGeffarth As said in the proposal, it would likely unnecessarily increase the size of your binary and dynamic linking would take longer. I really don't think it's too much hassle for the added clarity that you specifically mean for a particular thing to be used from Obj-C. – Hamish Jun 06 '17 at 12:54
  • So wrapping up: only solution would be to mark each individual function (requiring it) with `@objc`. If so, post that as an answer and I'll accept it. @Hamish. Thanks for your help! – LinusGeffarth Jun 06 '17 at 13:06
  • try this `#selector(ViewController.myAction)` – Anbu.Karthik Jun 06 '17 at 13:08
  • 1
    Tried, not working. – LinusGeffarth Jun 06 '17 at 13:11
  • Related question which gets to the core: https://stackoverflow.com/questions/44379348/the-use-of-swift-3-objc-inference-in-swift-4-mode-is-deprecated?rq=1 – kgaidis Sep 25 '17 at 13:01

5 Answers5

157

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)
    }

    @objc 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.

R. Rincón
  • 365
  • 2
  • 13
Hamish
  • 78,605
  • 19
  • 187
  • 280
  • 2
    Why does Xcode not automatically do that when converting the code to the latest syntax? – LinusGeffarth Jun 06 '17 at 13:54
  • @LinusGeffarth Hmm, it does for me – when going Edit > Convert > To Current Swift Syntax... and going through the migrator (with "minimise inference" selected), any selectors that refer to methods that aren't `@objc` get made `@objc`. Maybe try running the migrator again? – Hamish Jun 06 '17 at 14:01
  • Right, I should've tried that. Did it manually now. Thanks anyway. – LinusGeffarth Jun 06 '17 at 14:02
  • 1
    I wonder, are `@IBAction` also automatically `@objc`? that was not the case until now but it would be logical. EDIT: nvm, the proposal clearly states that `@IBAction` is enough for a method to be `@objc`. – Sulthan Jun 06 '17 at 15:43
  • @Sulthan Yes they are – this also appears to have been the case in Swift 3.1 as well. – Hamish Jun 06 '17 at 15:49
  • @Hamish I think that explicit `@objc` was required for `private` actions. – Sulthan Jun 06 '17 at 15:51
  • @Sulthan Hmm, no still working fine with `@IBAction private` in Swift 3.1 – Hamish Jun 06 '17 at 15:57
  • @Hamish You are right, there had to be a change I missed. – Sulthan Jun 06 '17 at 16:26
  • 8
    I don't understand any of this. I have no Objective-C-code what so ever. Is there no pure Swift-way of adding targets to a button? Or use selectors? I don't want my code to be riddled with this attribute. Is it because a UIButton is derived from some NSObject/Obj-c-thing? – Sti Jun 18 '17 at 00:02
  • @Sti Target-selector is an Obj-C pattern, and relies on the Obj-C runtime to work. You may not *directly* have any Obj-C code in your project, but under the hood on Apple platforms, Swift classes are built on Obj-C classes. They are therefore exposed to the Obj-C runtime (although members of the class aren't exposed by default – marking them as `@objc` does so). On other platforms, such as Linux, there is *no* Obj-C runtime, so you cannot, for example, use the `#selector` directive (you could consider this "pure Swift")... – Hamish Jun 18 '17 at 08:52
  • ... As said in my answer, by inheriting from `NSObject` (which brings a bunch of convenience methods for interacting with the Obj-C runtime), Swift used to assume that you wanted *all* (`internal` or higher) Obj-C compatible members of your class to be exposed to Obj-C by default. This is no longer the case – you have to specify which members to expose (or use `@objcMembers` to expose all again). – Hamish Jun 18 '17 at 08:52
  • 11
    @Sti So to directly answer "*Is there no pure Swift-way of adding targets to a button? Or use selectors?*" – no, there is no "pure Swift" way of using selectors to dispatch to methods. They rely on the Obj-C runtime in order to lookup the method implementation to call for a particular selector – the Swift runtime doesn't have that capability. – Hamish Jun 18 '17 at 08:56
  • 1
    @Hamish: Thanks a lot for the explanation. My problem -> After following the "fix" and got the additional *@obj* in front of my method, another warning comes up. I use Xcode version 9.0 beta 2 (9M137d). The warning is "The use of Swift 3 *@objc* inference in Swift 4 mode is deprecated. Please address deprecated *@objc* inference warnings, test your code with “Use of deprecated Swift 3 *@objc* inference” logging enabled, and disable Swift 3 *@objc* inference." – LukeSideWalker Jun 22 '17 at 17:07
  • 13
    Man selectors are a mess. It seems like every other swift update they are messing with it. Why can't we just have a pure swift autocomplete selector for methods. Makes code look so ugly. – John Riselvato Oct 10 '17 at 06:18
16

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).

Hamish
  • 78,605
  • 19
  • 187
  • 280
Kiran Sarvaiya
  • 1,318
  • 1
  • 13
  • 37
  • all I understand from this is that in some cases I need to add @objc to the start of a func whether or not I'm ever going to call it from Objective-C. I'm just learning Swift having used Obj-C for years – SundialSoft Aug 09 '18 at 09:04
11

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)
}
ricardopereira
  • 11,118
  • 5
  • 63
  • 81
Logan Sease
  • 372
  • 3
  • 5
  • 4
    This will also tell interface builder that this function can be connected, which might not be what you want. It's also doing the same thing as @objc. – Josh Paradroid Dec 19 '18 at 10:06
2

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 "Swift 3 @objc interface"

Hope this helps

S1LENT WARRIOR
  • 11,704
  • 4
  • 46
  • 60
2

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.

S1LENT WARRIOR
  • 11,704
  • 4
  • 46
  • 60
Renjish C
  • 49
  • 6