[NOTE This question was originally formulated under Swift 2.2. It has been revised for Swift 4, involving two important language changes: the first method parameter external is no longer automatically suppressed, and a selector must be explicitly exposed to Objective-C.]
Let's say I have these two methods in my class:
@objc func test() {}
@objc func test(_ sender:AnyObject?) {}
Now I want to use Swift 2.2's new #selector
syntax to make a selector corresponding to the first of these methods, func test()
. How do I do it? When I try this:
let selector = #selector(test) // error
... I get an error, "Ambiguous use of test()
." But if I say this:
let selector = #selector(test(_:)) // ok, but...
... the error goes away, but I'm now referring to the wrong method, the one with a parameter. I want to refer to the one without any parameter. How do I do it?
[Note: the example is not artificial. NSObject has both Objective-C copy
and copy:
instance methods, Swift copy()
and copy(sender:AnyObject?)
; so the problem can easily arise in real life.]
[NOTE This answer was originally formulated under Swift 2.2. It has been revised for Swift 4, involving two important language changes: the first method parameter external is no longer automatically suppressed, and a selector must be explicitly exposed to Objective-C.]
You can work around this problem by casting your function reference to the correct method signature:
(However, in my opinion, you should not have to do this. I regard this situation as a bug, revealing that Swift's syntax for referring to functions is inadequate. I filed a bug report, but to no avail.)
Just to summarize the new
#selector
syntax:The purpose of this syntax is to prevent the all-too-common runtime crashes (typically "unrecognized selector") that can arise when supplying a selector as a literal string.
#selector()
takes a function reference, and the compiler will check that the function really exists and will resolve the reference to an Objective-C selector for you. Thus, you can't readily make any mistake.(EDIT: Okay, yes you can. You can be a complete lunkhead and set the target to an instance that doesn't implement the action message specified by the
#selector
. The compiler won't stop you and you'll crash just like in the good old days. Sigh...)A function reference can appear in any of three forms:
The bare name of the function. This is sufficient if the function is unambiguous. Thus, for example:
There is only one
test
method, so this#selector
refers to it even though it takes a parameter and the#selector
doesn't mention the parameter. The resolved Objective-C selector, behind the scenes, will still correctly be"test:"
(with the colon, indicating a parameter).The name of the function along with the rest of its signature. For example:
We have two
test
methods, so we need to differentiate; the notationtest(_:)
resolves to the second one, the one with a parameter.The name of the function with or without the rest of its signature, plus a cast to show the types of the parameters. Thus:
Here, we have overloaded
test(_:)
. The overloading cannot be exposed to Objective-C, because Objective-C doesn't permit overloading, so only one of them is exposed, and we can form a selector only for the one that is exposed, because selectors are an Objective-C feature. But we must still disambiguate as far as Swift is concerned, and the cast does that.(It is this linguistic feature that is used — misused, in my opinion — as the basis of the answer above.)
Also, you might have to help Swift resolve the function reference by telling it what class the function is in:
If the class is the same as this one, or up the superclass chain from this one, no further resolution is usually needed (as shown in the examples above); optionally, you can say
self
, with dot-notation (e.g.#selector(self.test)
, and in some situations you might have to do so.Otherwise, you use either a reference to an instance for which the method is implemented, with dot-notation, as in this real-life example (
self.mp
is an MPMusicPlayerController):...or you can use the name of the class, with dot-notation:
(This seems a curious notation, because it looks like you're saying
test
is a class method rather than an instance method, but it will be correctly resolved to a selector nonetheless, which is all that matters.)