Swift Regex Deep Dive
iOS MacOur introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
Just want a reminder of the syntax? Jump to the TL;DR.
The selector
is key to Objective-C’s dynamic runtime nature. It’s
just a name that’s used, at runtime, as a key into a dictionary of function
pointers. Whenever you send a message to an Objective-C object,
you’re actually using a selector to look up a function to
call. Sometimes selectors bubble up in Cocoa/CocoaTouch API, and you
will need to deal with them in Swift.
In Cocoa/CocoaTouch, selectors are a way of telling an object like a
UIButton “When someone taps you, I want you to send this message to
this particular object.” The name of the method to invoke,
and the object to send the message to, are potentially not
known until runtime. Most of the time these connections are made in a
xib file or a storyboard, but you can do it in code too:
UIButton *button = [UIButton buttonWithType: UIButtonTypeInfoLight];
[button addTarget: self
action: @selector(showAboutBox:)
forControlEvents: UIControlEventTouchUpInside];
Tapping the button causes self
to be sent the message
showAboutBox:
. If there is no showAboutBox:
method, you’ll die
at runtime with an undefined selector exception.
@selector(message:name:argument:names:too:)
is an Objective-C
compiler feature that turns a sequence of characters that happens to
look like an Objective-C method name into some key useful for looking
up code at runtime. There is no type information encoded in the
selector, outside of the number of colons indicating the number of
arguments expected by the method.
This kind of API is common with Cocoa/CocoaTouch’s “target/action”
architecture, used in most of the UI controls, as well as with
NSNotificationCenter. This is a fairly old design—APIs
that take blocks/closures are more common these days.
Why this kind of convoluted machinery of storing a name and then later
looking it up at runtime? It lets you use very descriptive names for
handler functions. You can easily figure out what openAboutBox:
would do. If you had to subclass a Button class and override
tapped()
all the time, it would be harder to determine at a glance
what the button handler does. It also allows tooling, such as
Interface Builder, to set up these connections without having to
generate code. It just saves the selector name in a resource file.
You use Cocoa/CocoaTouch from Swift, and so you want button taps to
invoke Swift code. Swift needs to be able to use selectors. Selectors
aren’t part of the default Swift runtime behavior, because sending
arbitrary messages to objects can be unsafe. The compiler can’t guarantee
that the receiving object actually responds to that selector.
To have your Swift objects participate in the Objective-C runtime, you
will need to opt-in by having your class inherit from NSObject
, or
decorate individual methods with @objc
. This makes a Swift class
participate in Objective-C’s method dispatch mechanism. If you’re
curious about how Objective-C’s runtime does its thing, check out the
Inside the
Bracket
extravaganza.
Here’s the equivalent code in Swift for adding a new callback to a UIButton:
button.addTarget(self,
action: #selector(GroovyViewController.showAboutBox),
forControlEvents: .TouchUpInside)
#selector
is Swift’s equivalent of @selector
. The #
sigil serves
the same purpose as @
in Objective-C: here comes some compiler
magic, such as how
#available
guards code to protect it from running on too-old systems. In this
case, #selector
is used to construct an Objective-C selector given a
description of a Swift function. You can see all the details about
#selector
in swift-evolution proposal SE-0022 – Referencing the
Objective-C selector of a
method.
Why not just use a string, like “showAboutBox,” similar to how
@selector
works? Method names get automatically renamed going
between Objective-C and Swift—sometimes an argument name goes before
the opening paren, sometimes after. Maybe there’s an NSError**
involved. Remembering all the rules is tedious and error-prone. If
you spend all your time in Swift, you don’t need to juggle a bunch of
Objective-C details in your head. Sounds like a great job for a
compiler, though.
When Swift sees #selector
it examines the
method being referenced, and then derives a selector from it. Any
necessary name rewriting is done automatically. This is why you usually
supply the class name to #selector
—the compiler can unambiguously
determine the information it needs about this particular method, such as, “Does
this thing actually exist?” or “Has the selector name been explictly renamed?”
If the method you’re getting a #selector for is defined in the same class where
you’re referencing it (say in a view controller’s viewDidAppear
referencing
methods in that same view controller), you can leave off the explicit class
name. Be aware that Xcode won’t properly autocomplete it for you, instead
only offering a function call-site rather than just a reference to the function.
For the rest of this post, I’ll be using the fully-qualified form.
Objective-C’s selector syntax is pretty simple – it just uses
@selector(methodName:arguments:)
. Swift’s is a bit more complicated.
In its simplest form, you need to provide a class name that defines
(or inherits) the message, and then the method name without any extra
decoration:
#selector(UIView.setNeedsDisplay)
#selector(GroovyViewSubclass.setNeedsDisplay)
It doesn’t matter how many arguments these methods take—the compiler
will synthesize the correct name with the correct number of colons in
the selector.
Swift has function overloading (same name, different arguments), while
Objective-C does not. You may need to disambiguate which Swift
function you want the selector to refer to.
Here’s an overloaded doStuff
method:
class Thing {
...
func doStuff(stuff: Int) {
print("do Stuff (stuff)")
}
func doStuff(stuff: Double, fluffy: String) {
print("do Stuff (stuff) - (fluffy)")
}
When you call doStuff
directly, Swift can figure out which one of
these to use based on the arguments you pass. Indirect calling by
selector doesn’t have the luxury of knowing the argument’s types. If
you try to make a selector for doStuff
, you will get an error about
ambiguous use of doStuff
:
The way you fix this is to include argument labels:
#selector(Thing.doStuff(_:fluffy:)))
This says to build a selector that references the two-argument form of
doStuff
, rather than the one-argument form. You can get more
details on this naming convention in swift-evolution proposal
SE-0021 – Naming Functions with Argument
Labels.
Swift classes that participate in the Objective-C runtime have
selectors automatically built by the compiler. The Swift compiler
bases the selectors on the method’s name. Sometimes you may want to
expose a different name to Objective-C, a
name that’s more in line with what an Objective-C developer is
expecting.
You might also have overloaded functions that don’t differ by number
of arguments (just types). The selector then becomes ambiguous.
Here’s a pure-Swift class:
class Blorf {
func takesAnArgument(fluffy: String) {
print("in takes an argument: (fluffy)")
}
func takesAnArgument(fluffy: Double) {
print("taking a double (fluffy)")
}
}
This works fine in your app. Then you decide to inherit from NSObject
so a Blorf object can receive UIButton taps:
Objective-C demands that all methods have unique selectors. Objective-C selectors don’t carry type information, so the selectors
for both takesAnArgument
methods will be the same: @selector(takesAnArgument:)
. You either have
to rename one of them, or tell the compiler to keep the Swift name and
use a different name for the Objective-C selector:
@objc(takesADoubleArgument:)
func takesAnArgument(fluffy: Double) {
print("taking a double (fluffy)")
}
This fixes the compile error.
There’s still one problem, though. Take a look at what you get when
you autocomplete takesAnArgument
while trying to make a #selector
:
Autocomplete doesn’t show type information. And we’re right back to
ambigousnessland:
So the options now are to rename the method, or use the full
#selector
to refer to a method unambiguously:
let selector = #selector(((Blorf.takesAnArgument(_:)) as (Blorf) -> (Double) -> Void))
That syntax is kind of intense. Here it is broken down:
#selector(...)
– here comes a selector
(Blorf.takesAnArgument(_:))
– This is the Swift method name name
that you want a selector for.
(... as (Blorf) ->
– this is the curried self, also known as the
instance self. Just think of this as the type of a hidden
parameter which ends up being self
inside of the method.
... (Double) -> Void)
– this is the signature of the method
without the self
parameter. In this case, this selector
references the version of takesAnArgument
that takes a Double and
returns nothing. This is the one that was decorated with
@objc(takesADoubleArgument:)
, so the selector ultimately generated
by this expression would be an Objective-C
@selector(takesADoubleArgument:)
Swift 3 extends the #selector
syntax with getter:
and setter:
arguments. When determining a selector for an Objective-C propery, you need to specify
whether you want the setter or getter. Use it like this:
let sel = #selector(getter: UIViewController.preferredContentSize)
#selector
was introduced in Swift 2.2. Prior versions of Swift used a
String mechanism to automatically convert selector-looking strings
into selectors. If you use this syntax today, you will get a
deprecation warning.
You can also use Selector("someSelectorName")
as an alternative syntax,
but the compiler will give you a warning suggesting you use #selector
,
assuming it has seen this selector anywhere before. You can use this syntax to
generate a selector that the compiler hasn’t seen yet, perhaps as a
selector to methods that are dynamically loaded by plugins at
runtime.
Remember that selectors in Objective-C don’t carry along type
information. The #selector
syntax is just used to derive the proper
Objective-C selector to reference a particular Swift method. There is
no safety checking performed. This means you’re welcome to create a
Swift method that takes 17 generic enum parameters and use it for a
UIButton action. You’re also welcome to crash hard at runtime. Swift doesn’t make using selector-based API foolproof.
Here’s the syntax, as of Swift 2.2, for selectors. Unindented lines
indicate what particular kinds of methods are being #selector
ed, and
indented lines are using actual types. The first form includes the class name.
If you’re getting a selector for something in the current class (or a superclass), you can use the second, more compact form. Be aware that Xcode (at least as of 7.3) won’t properly autocomplete that form.
Basic selector:
#selector(AnotherClassName.unambiguousFunction)
#selector(ViewController.showAboutBox)
#selector(unambiguousFunctionInThisClass)
#selector(showAboutBox)
Property getters and setters:
#selector(getter|setter: AnotherClassName.propertyName)
#selector(getter: ViewController.isEditing)
#selector(setter: ViewController.isEditing)
#selector(getter|setter: propertyInThisClass)
#selector(getter: isEditing)
#selector(setter: isEditing)
Choosing between overloads with different argument lists:
#selector(AnotherClassName.overloadedFunction(_:label:label:))
#selector(BadgerViewController.logStuff(_:args))
#selector(overloadedFunctionInThisClass(_:label:label:))
#selector(logStuff(_:args))
Choosing between overloads that have the same argument list:
#selector(((AnotherClassName.ambiguousOverload(_:)) as (AnotherClassName) -> (ArgumentList) -> ReturnType))
#selector(((Blorf.takesAnArgument(_:)) as (Blorf) -> (Double) -> Void))
#selector(((ambiguousOverload(_:)) as (ArgumentList) -> ReturnType))
#selector(((takesAnArgument(_:)) as (Double) -> Void))
Want to learn more about Swift and Objective-C Interoperability?
Join our next Advanced iOS
Bootcamp!
Our introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
The Combine framework in Swift is a powerful declarative API for the asynchronous processing of values over time. It takes full advantage of Swift...
SwiftUI has changed a great many things about how developers create applications for iOS, and not just in the way we lay out our...