The Use of Swift 3 @Objc Inference in Swift 4 Mode Is Deprecated

Swift 3 @objc interference issue not being fixed :-(

  1. Click on the project (the first thing in the massive drop-down list)
  2. Click on the Targets section that appears
  3. Search for the Swift 3 @objc interference section
  4. Turn it On and fix any warnings/errors that pop up
  5. Turn it to Default and your project should run without any issues

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

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() {
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:

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.

Why do I have to use public to expose func with Swift 4 @objc inference

I think these are two different things.

"Swift declarations marked with the private or fileprivate modifier do
not appear in the generated header.

is Followed by

Private declarations are not exposed to Objective-C unless they are explicitly marked with @IBAction, @IBOutlet, or @objc.

This can be read as saying a function must have the explicit marking to be exposed to Objective-C, and (separately) if marked private it will not be included in the header.

I'm not sure why something would need to be exposed without being visible, (I'm not familiar enough with the runtime to speak to that). However the closest Objective-C analogue to private is to not be included in the header, so it makes sense to have that behavior match.

Related Topics

Leave a reply