Back to blog

Target-Action Explained in Swift


Aasif Khan
By Aasif Khan | Last Updated on July 12th, 2023 4:04 pm | 5-min read

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 app development 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

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:

  1. The target is self
  2. The action is #selector(fireworks) (a so-called selector)
  3. The event is .touchUpInside (from UIControl.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 signature fireworks (no arguments, no parentheses)
  • The function fireworks(intensity: Int) has signature fireworks(intensity:) (parentheses, argument label followed by colon)
  • The function call fireworks(intensity: Int, size: Int) has signature fireworks(intensity:size:) (parentheses, multiple labels with colons)
  • When a parameter is unnamed, its label is _, in the function call fireworks(_ intensity: Int, color: UIColor) which has signature fireworks(_: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 @objc 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.


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts