Swift Selectors: Everything You Need to Know
If you're an Objective-c developer and don't have a good grasp of selectors, you'll have a hard time. Thankfully, most of that is behind us with Swift. However, there are a few implementations of selectors in Swift that you should be able to take advantage of today.
So, in this article, we'll explore Objective-c selectors. You'll learn what they are and how to use them in Swift.
We'll begin by defining what a selector is, how it works, and why it exists. Then we'll address the reason selectors are still a part of Swift and explore some of the common uses of selectors on the platform. Finally, we'll provide you with some common examples of selectors in Swift and how to implement them properly.
Alright, let's get into it.
What Is a Selector?
Apple defines a selector as "a type that refers to the name of an Objective-C method." It adds that "in Swift, Objective-C selectors are represented by the Selector structure, and you create them using the #selector expression."
Now, this doesn't do a great job clarifying what it is. So, here's a more down-to-earth explanation: In simple terms, a selector is a string that represents the signature of a generic method in a class.
Strongly typed languages won't let you run code with a type mismatch or call to a method that doesn't exist. However, Objective-c allows you to create objects without the class reference or call methods by knowing the method name even if Objective-c does not know the object class beforehand. This last feature is what selectors accomplish. You can send generic messages to objects without knowing their type.
Now, how does this work? In essence, every time you send a message to an Objective-C object, it is compiled into a call to an Objective-C runtime function called objc_msgSend().
This function takes several arguments:
- self: The object you sent the message to on the call.
- cmd: A selector representing the message you sent and the method you intended to call.
- arg: The remaining arguments of the initial method call.
To illustrate, here's some Objective-C code:
That code would become the following in C:
This 'objc_msgSend' function uses the pointer 'myObject' to identify the class 'myObject'. Once it's found, the runtime searches for the method implementation in the class corresponding to the message represented in the selector and calls it.
When using selectors, you must be careful not to send a message to an object that doesn't have it defined. It's vital to ensure that the message target is an instance of a class (or a class object) that implements a method corresponding to the selector. Otherwise, you end up with an unrecognized selector exception.
Selectors (and protocols for that matter) allow controllers to manage and observe themselves and other controllers. Additionally, it allows for easy state data manipulation between controllers. This is one of the hallmarks of the power and dynamic nature of Objective-c.
Selectors in Swift
As you probably know, Swift is a strongly typed language, and it won't let you call a method on an object that doesn't implement it or inherits it from a superclass or extension.
But even in a pure Swift project, there are some cases where using selectors is necessary to access some of the native interfaces in cocoa, like the target/action pattern to UI controls found on the Timer and UIBarButtonItem elements. Additionally, you have to use selectors if you need to register a notification observer with NSNotificationCenter. This process is necessary because you have to tell the notification center what method to call and on what listener object to call it on when the user interacts with the control or the system posts the notification.
Let's illustrate with an example.
The following code contains a simple timer that requires you to provide the operation that Swift should perform once the specified interval ends.
Notice that the target and selector parameters contain an instance of the object containing the method with the operation and the selector specifying the method itself.
In Swift, you use the '#selector' directive to create Objective-c selector strings from strongly typed class objects. This method allows you to retain the convenience and security of types while safely using the selectors.
Additionally, to reference the class method in the selector, you must label it with the '@objc' directive, which makes it visible to Objective-c.
Parameters in Selectors
OK, but what about parameters?
That's a bit more complicated. In the particular case of the Timer, you can use the 'userinfo' parameter to pass data to the target method.
This process is cumbersome and verbose, but it does the job.
However, all this is pointless because you can easily create a timer with a closure.
Nevertheless, selectors have been on a steady decline since the earliest versions of Swift didn't include some core selector functionality.
Common Selectors in Swift
Here are some common use cases for selectors in Swift and how to implement them properly.
For this particular example, it was necessary to add a class that has additional properties defined to be able to pass parameters to the target operation. However, it all works essentially the same.
Here you can see that the implementation is quite streamlined and straightforward. However, keep in mind that this is a UIKit element and not the SwiftUI button element, which doesn't use selectors but closures.
In this example, you only need to pass the object reference, the selector, and the notification name. Notice that the parameter in the target method changed to match the sender type. This will help you retrieve additional information passed to the method.
One of the benefits of working with modern technologies like Swift is the robustness and versatility it offers with its strongly typed and elegant syntax. As a fresh developer coming from other modern programming languages, this might not be that remarkable. Still, this is a big deal for those who came from languages like Objective-c, Java, or C.
So much of the struggle to build a solid and complex application used to come from making sure that the project would build and that no type errors would be found. It was tedious work that required a lot of expertise and deep knowledge of the architecture and logic behind the project.
Objective-c had its own challenges regarding memory allocation and, more importantly, selectors. Thankfully, Swift has come a long way, and, for the most part, selectors will be a thing of the past soon.
However, if you want to guarantee that your projects are bug-free and ready for prime time now, you must have a reliable testing workflow in place. This requirement can be expensive and time-consuming, especially for tight teams with many responsibilities. So, we advise using Waldo's zero-code testing workflow solution. You don't need to hire more engineers or take any of your team's productive time. Just set it up, and it's good to go. You can learn more about it here.