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.
@objc
is now necessary in order to expose them to Obj-C, and therefore use with selectors. – Hamish@objc
? That is a bit annoying, but I generally make these functions private, requiring me to mark it as@objc
anyways. – Connor Neville@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