Target-Action Explained in Swift
Target-Action is a mechanism on iOS that’s used to call a certain function on a certain object. It’s prominently used in many iOS frameworks and libraries, although you may not have given it any thought until now. Let’s find out how it works!
In this tutorial, you’ll learn:
- What target-action is, and what it’s used for
- What
self
means, when using it with target-action - How to deal with selectors and function signatures
- How Objective-C and Swift interoperability affects target-action
- Uses for target-action in practical iOS development
Ready? Let’s go.
- What is Target-Action?
- What is “self”?
- Using Selectors in Swift
- Target-Action in Practical iOS Development
- Further Reading
What is Target-Action?
Consider the idea that programming is nothing more than giving commands to things. Give commands to many things, and respond to user interaction, and you’ve got yourself an app (sort of).
Here’s one such command:
bob.kicks(ball)
In the above code, the function kicks()
is called on the object bob
. And the function kicks()
takes one parameter ball
. Bob kicks the ball. This is fairly basic programming, right?
Next up, imagine we’re building an app that has one button. Its variable name is button
and its type is UIButton
, which is part of the UIKit
framework. When the button is tapped by a user, we want to execute a certain function named fireworks()
.
How are we going to tell button
to call the fireworks()
function when it’s tapped? That’s where target-action comes in.
Let’s take one step back for a second. Remember the bob.kicks(ball)
example? What happens there is actually called messaging. When you code that line in Swift, what you’re really doing is sending a message “kicks”, with an argument “ball”, to the bob
object.
With target-action, you’re doing the same thing. Sending a message, the action, to an object, the target.
Let’s look at an example for UIButton
. First, we create the button, and set the text on the button:
let button = UIButton(type: .plain)
button.setTitle("Launch fireworks!", for: .normal)
Then, we’re using the target-action mechanism:
button.addTarget(self, action: #selector(fireworks), for: .touchUpInside)
A few things happen in that one line of code. We’re calling the function addTarget(_:action:for:)
on button
with 3 parameters:
- The target is
self
- The action is
#selector(fireworks)
(a so-called selector) - The event is
.touchUpInside
(fromUIControl.Event
)
It works like this: when the event .touchUpInside
happens, send a message to self
to execute function fireworks()
.
And here’s that fireworks()
function, for the sake of completeness:
@objc func fireworks() {
print("BOOOOM!!! PFEEEEUUUWWW!!! KKKKGGGRRRR!!!")
}
Why can’t button
just execute the function fireworks()
?
Well, consider that the UIButton
doesn’t know your code. It doesn’t know you have that function, and moreover, it can’t access your function directly! You need a way to tell the button what to do, and you do that by giving it a target and an action. You can think of it as a “reference” to the function, which is saved for calling at a later point in time.
The .touchUpInside
constant comes from the UIControl.Event struct, which is part of the UIControl
class. Target-action is mostly used in UIControl
subclasses, like UIButton
. The struct defines many control events, such as .valueChanged
, .editingDidEnd
and .touchDown
.
The .touchUpInside
event is used for a tap interaction on, say, a button. When you lift your finger again after a tap, the .touchUpInside
(“touch up inside [item]”) event is fired. This event is very common.
Up next, let’s take a closer look at self
.
What is “self”?
The self
keyword refers to the current class instance, from within that class instance.
In Object-Oriented Programming, your code is organized in classes. You can create objects from those classes, much like how you can create buildings from a blueprint.
Let’s look at an example. First, we’re creating a class:
class Vehicle {
var wheels = 0
var position = 0
}
Then, we’re creating an instance of that class:
let car = Vehicle()
car.wheels = 4
Within any function inside the Vehicle
class, you can use self
to refer to the current instance of the Vehicle
. Like this:
func drive(to position: Int) {
self.position = position
}
In the above code you’re using self
to differentiate between the property position
and the parameter position
. The keyword self
is a value that refers to the current class instance, a value you can work with from within the class.
Check this out:
let car = ···
car.drive(to: 999)
In the above code, the self
inside the drive(to:)
function refers to the exact same object as car
. From inside the Vehicle
class, self
refers to the current instance of the class.
But there’s another use for self
. You can pass it around in your code, just like any other value or reference:
button.addTarget(self, action: #selector(fireworks), for: .touchUpInside)
With the above code you pass self
to button
, so button
now has a reference to the current class instance. It can now use that reference to call the fireworks()
function on the object that self
refers to. Inside the addTarget(···)
function it’s not called self
anymore of course, but probably something like target
.
Internally, and with Objective-C, calling that selector looks something like this:
if([target respondsToSelector: selector]) {
[target performSelector: selector];
}
This Objective-C code first checks if a given selector
is a valid function for a certain target
, and then calls that selector on the target. This is effectively the same as:
target.selector()
Except that the actual selector, i.e. the function that’s called, is passed around as a value!
A target object is typically a controller, such as a view controller. You’ve added a button to a view, and you a function to its view controller, and you want that function executed when the button is tapped. In that case, the target object will be the view controller. And given that you’re coding all this inside the view controller class, the target will be self
.
Can you also pass something else than self
? Yes! You can pass in any value as the target object. A routing controller could pass another controller object to addTarget(_:action:for:)
, for instance, and let that controller object handle events.
Using Selectors in Swift
What’s up with selectors, anyway? A selector is used to describe the name of a function.
It’s easiest to compare selectors to writing down an instruction on a piece of paper, and passing that on to someone else. Instead of directly telling them what to do (“kick the ball”), you’re writing “kick the ball” on a piece of paper, with the idea that someone can read that and execute the command.
Swift doesn’t use selectors, but Objective-C does. And because many iOS frameworks are written in Objective-C, and you use those in conjunction with Swift, you use selectors in Swift.
Before Swift 2.2, you’d use strings as selectors. Like this:
Selector("functionToExecute:")
This has a major disadvantage: you can’t check if a selector is valid during compilation. This often leads to the dreaded unrecognized selector sent to instance error.
So, since Swift 2.2+ you can use the #selector(...)
syntax like this:
#selector(functionToExecute(_:))
The above selector is a reference to a function, which can be checked at compile-time. Prior to running the app, Swift checks if the selector (“function”) actually exists. If it doesn’t, you’ll see an error message. This reduces bugs, because you can catch them early on.
What’s interesting about selectors is that they use a special signature format to describe functions. You’ve probably seen it before, because it’s used throughout Apple’s documentation and iOS tutorials.
In short, the signature uses the function name and zero, one or more argument labels between parentheses. A few examples:
- The function
fireworks()
has signaturefireworks
(no arguments, no parentheses) - The function
fireworks(intensity: Int)
has signaturefireworks(intensity:)
(parentheses, argument label followed by colon) - The function call
fireworks(intensity: Int, size: Int)
has signaturefireworks(intensity:size:)
(parentheses, multiple labels with colons) - When a parameter is unnamed, its label is
_
, in the function callfireworks(_ intensity: Int, color: UIColor)
which has signaturefireworks(_:color:)
for instance
Quick Tip: Use Xcode’s autocomplete function to get the right function signature. Start typing #selector(
and then hit the Esc
-key. A dropdown with possible function matches appears. Select the right function, and the right signature is added to your code automatically.
Depending on your taste and coding style, using selectors in Swift code can get quite messy. Especially if a function signature is long. To solve that, you could create an extension for the Selector
struct. Like this:
extension Selector {
static let fireworks = #selector(Effects.fireworks)
static let onButtonTapped = #selector(ViewController.onButtonTapped(_:))
}
You can either put every selector in one extension (bad idea), or mark the extension as fileprivate
to only make it available in one file (and class).
You can now use the selector as an inferred constant, like this:
button.addTarget(self, selector: .onButtonTapped, for: .touchUpInside)
Although it adds some obscurity to your code, it greatly improves readability. Make sure you’re documenting what you did with this extension though, otherwise you’re in for a treat when refactoring your code.
Don’t forget to add the attribute to a function that you want to use with target-action. This will make the function available to Objective-C, which is required because
UIControl
(and target-action) is an Objective-C class.
Target-Action in Practical iOS Development
So far we’ve looked at what the target-action mechanism is, how it works, and we’ve taken a detour into selectors. The remaining question is of course: How does target-action affect practical iOS development? Let’s dive in.
The target-action is most prominent in a few iOS frameworks, most notably UIKit
. Pretty much every UI component uses target-action to respond to user input.
Consider UIButton
, the class that we’ve examined before. It uses target-action to respond to user taps on the button, with the .touchUpInside
control event.
A similar UI element is the UISwitch
, an on-off switch that you can “flip”. This class generates a .valueChanged
control event when the switch is toggled. You can respond to this event by using target-action with the addTarget(_:action:for:)
function.
Another class that uses the target-action mechanism is NotificationCenter. With NotificationCenter
you can broadcast notifications to observers, with the Observer-Observable design pattern. It’s perfect for broadcasting events between one or more unrelated components of your app.
And guess how you tell NotificationCenter
to execute a particular function when a notification has been received? With the target-action mechanism. Like this:
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveData(_:)), name: .didReceiveData, object: nil)
In the above code, the first unnamed argument self
is the target (or observer) and the selector
parameter takes a selector value. When the notification with name .didReceiveData
is observed, the function onDidReceiveData(_:)
is called on self
.
Another component in iOS development uses target-action: timers. In this case, you’re scheduling a timer and telling the Timer
object what function to call when the timer runs out and fires. Its syntax should feel familiar by now:
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fire), userInfo: nil, repeats: true)
The good thing about target-action is that if you’ve seen it once, you’ll see it everywhere. And the syntax is consistent: there’s always an action, a selector that describes a function that gets called on an object, the target.
The dynamic way target-action connects parts of your code to other parts, and instructs an object to execute a function, has a disadvantage: what if that function doesn’t exist?
That’s where the compile-time checking of #selector(...)
with Swift 2.2+ comes in. Before you run your code, when it’s compiling, the compiler checks if referenced functions actually exists. When a function’s missing, you’ll see an error and your app won’t start. And that helps you avoid bugs that are particularly hard to fix!
The target-action mechanism is becoming a legacy component on iOS, a remnant from Objective-C. It’s still used a lot today, for example in UIKit, but we’re seeing it getting replaced in the iOS SDKs with more novel approaches like closures.
Further Reading
The target-action mechanism isn’t something you dwell on every day, but it’s worthwhile to investigate how it works. It’s an integral part of Objective-C SDKs, and you can’t get around using it in Swift.
In this tutorial, we’ve discovered how target-action works, how you can use it, and how it affects practical iOS development.
Want to learn more? Check out these resources: