Search

Hannibal #selector

Mark Dalrymple

8 min read

Jul 6, 2016

Hannibal #selector

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.

Selectors in Swift

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.

Xcode completing a `#selector` without a class name component, suggesting a function call

For the rest of this post, I’ll be using the fully-qualified form.

Selector Syntax

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:

Error: 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
.

The Rename Game

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:

Error: Method takesAnArgument with ObjC selector conflicts with previous declaration with the same ObjC selector

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.

All The Things

There’s still one problem, though. Take a look at what you get when
you autocomplete takesAnArgument while trying to make a #selector:

Xcode autocomplete shows two identical takesAnArgument(_:) suggestions

Autocomplete doesn’t show type information. And we’re right back to
ambigousnessland:

Error: Abiguous use of 'takesAnArgument'

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 Additions

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)

Older Syntax

#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.

Danger!

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.

TL;DR

Here’s the syntax, as of Swift 2.2, for selectors. Unindented lines
indicate what particular kinds of methods are being #selectored, 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
!

Mark Dalrymple

Author Big Nerd Ranch

MarkD is a long-time Unix and Mac developer, having worked at AOL, Google, and several start-ups over the years.  He’s the author of Advanced Mac OS X Programming: The Big Nerd Ranch Guide, over 100 blog posts for Big Nerd Ranch, and an occasional speaker at conferences. Believing in the power of community, he’s a co-founder of CocoaHeads, an international Mac and iPhone meetup, and runs the Pittsburgh PA chapter. In his spare time, he plays orchestral and swing band music.

Speak with a Nerd

Schedule a call today! Our team of Nerds are ready to help

Let's Talk

Related Posts

We are ready to discuss your needs.

Not applicable? Click here to schedule a call.

Stay in Touch WITH Big Nerd Ranch News