- Proposal: SE-0022
- Author: Doug Gregor
- Review Manager: Joe Groff
- Status: Implemented (Swift 2.2)
- Decision Notes: Rationale
- Implementation: apple/swift#1170
In Swift 2, Objective-C selectors are written as string literals
(e.g., "insertSubview:aboveSubview:"
) in the type context of a
Selector
. This proposal seeks to replace this error-prone approach
with Selector
initialization syntax that refers to a specific method
via its Swift name.
Swift-evolution thread: here, Review, Amendments after acceptance
The use of string literals for selector names is extremely error-prone: there is no checking that the string is even a well-formed selector, much less that it refers to any known method, or a method of the intended class. Moreover, with the effort to perform automatic renaming of Objective-C APIs, the link between Swift name and Objective-C selector is non-obvious. By providing explicit "create a selector" syntax based on the Swift name of a method, we eliminate the need for developers to reason about the actual Objective-C selectors being used.
Introduce a new expression #selector
that allows one to build a selector from a reference to a method, e.g.,
control.sendAction(#selector(MyApplication.doSomething), to: target, forEvent: event)
where “doSomething” is a method of MyApplication, which might even have a completely-unrelated name in Objective-C:
extension MyApplication {
@objc(jumpUpAndDown:)
func doSomething(sender: AnyObject?) { … }
}
By naming the Swift method and having the #selector
expression do
the work to form the Objective-C selector, we free the developer from
having to do the naming translation manually and get static checking
that the method exists and is exposed to Objective-C.
This proposal composes with the Naming Functions with Argument Labels proposal, which lets us name methods along with their argument labels, e.g.:
let sel = #selector(UIView.insertSubview(_:atIndex:)) // produces the Selector "insertSubview:atIndex:"
With the introduction of the #selector
syntax, we should deprecate
the use of string literals to form selectors. Ideally, we could
perform the deprecation in Swift 2.2 and remove the syntax entirely
from Swift 3.
Additionally, we should introduce specific migrator support to
translate string-literals-as-selectors into method references. Doing
this well is non-trivial, requiring the compiler/migrator to find all
of the declarations with a particular Objective-C selector and
determine which one to reference. However, it should be feasible, and
we can migrate other references to a specific, string-based
initialization syntax (e.g., Selector("insertSubview:atIndex:")
).
The subexpression of the #selector
expression must be a reference to an objc
method. Specifically, the input expression must be a direct reference to an Objective-C method, possibly parenthesized and possibly with an "as" cast (which can be used to disambiguate same-named Swift methods). For example, here is a "highly general" example:
let sel = #selector(((UIView.insertSubview(_:at:)) as (UIView) -> (UIView, Int) -> Void))
The expression inside #selector
is limited to be a series of instance
or class members separated by .
where the last component may be
disambiguated using as
. In particular, this prohibits performing
method calls inside #selector
, clarifying that the subexpression of
#selector
will not be evaluated and no side effects occur because of it.
The complete grammar of #selector
is:
selector → #selector(selector-path) selector-path → type-identifier . selector-member-path as-disambiguationopt selector-path → selector-member-path as-disambiguationopt selector-member-path → identifier selector-member-path → unqualified-name selector-member-path → identifier . selector-member-path as-disambiguation → as type-identifier
The introduction of the #selector
expression has no
impact on existing code. However, deprecating and removing the
string-literal-as-selector syntax is a source-breaking
change. We can migrate the uses to either the new #selector
expression or to explicit initialization of a Selector
from a string.
The primary alternative is type-safe
selectors,
which would introduce a new "selector" calling convention to capture
the type of an @objc
method, including its selector. One major
benefit of type-safe selectors is that they can carry type
information, improving type safety. From that discussion, referencing
MyClass.observeNotification
would produce a value of type:
@convention(selector) (MyClass) -> (NSNotification) -> Void
Objective-C APIs that accept selectors could provide type information
(e.g., via Objective-C attributes or new syntax for a typed SEL
),
improving type safety for selector-based APIs. Personally, I feel that
type-safe selectors are a well-designed feature that isn't worth
doing: one would probably not use them outside of interoperability
with existing Objective-C APIs, because closures are generally
preferable (in both Swift and Objective-C). The cost of adding this
feature to both Swift and Clang is significant, and we would also need
adoption across a significant number of Objective-C APIs to make it
worthwhile. On iOS, we are talking about a relatively small number of
APIs (100-ish), and many of those have blocks/closure-based variants
that are preferred anyway. Therefore, we should implement the simpler
feature in this proposal rather than the far more complicated (but
admittedly more type-safe) alternative approach.
Syntactically, @selector(method reference)
would match Objective-C
more closely, but it doesn't make sense in Swift where @
always
refers to attributes.
The original version of this proposal suggested using a magic
Selector
initializer, e.g.:
let sel = Selector(((UIView.insertSubview(_:at:)) as (UIView) -> (UIView, Int) -
However, concerns over this being magic syntax that looks like
instance construction (but is not actually representable in Swift as
an initializer), along with existing uses of #
to denote special
expressions (e.g., #available
), caused the change to the #selector
syntax at the completion of the public review.
- 2016-05-20: The proposal was amended post-acceptance to limit the
syntax inside the subexpression of
#selector
, in particular disallowing methods calls. Originally any valid Swift expression was supported.