Waldo sessions now support scripting! – Learn more
App Development

Swift Selectors: Everything You Need to Know

Juan Reyes
Juan Reyes
Swift Selectors: Everything You Need to Know
June 28, 2022
8
min read

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.

Strongly typed languages won't let you run code with a type mismatch or call to a method that doesn't exist.

Objective-c Selector

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:


[myObject doCalculation: @"TestText" and: 30];

That code would become the following in C:


objc_msgSend(myObject, “doCalculation:and:”, @“TestText”, 30);

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.


import UIKit
let object = MyClass()
Timer.scheduledTimer(timeInterval: 1,
                                 	target: object,
                                 	selector: #selector(MyClass.sayHi),
                                 	userInfo: nil,
                                 	repeats: true)
class MyClass {
    @objc
    func sayHi() {
        print("Hi there!")
    }
}

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.

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.

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.


import UIKit
let object = MyClass()
Timer.scheduledTimer(timeInterval: 1,
                     				target: object,
                     				selector: #selector(MyClass.sayHiTo),
                    		 		userInfo: ["name" : "Juan",
                                					"age" : 34],
                     				repeats: true)
class MyClass {
    @objc
    func sayHiTo(sender: Timer) {
        let data: [String : AnyObject] = sender.userInfo! as! [String : AnyObject]
        let name: String = data["name"] as! String
        let age: Int = data["age"] as! Int
        print("Hi there \(name)! I know you are \(age) years old now.")
    }
}

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.


import UIKit
let name = "Juan"
let age = 34
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { sender in
    print("Hi there \(name)! I know you are \(age) years old now.")
}

Oh well.

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.

TapGesture


import UIKit
let object = MyClass()
let tapGesture = CustomTapGestureRecognizer(target: object,
                                            action: #selector(MyClass.sayHiTo(sender:)))
tapGesture.myname = "Juan"
class MyClass {
    @objc
    func sayHiTo(sender: CustomTapGestureRecognizer) {
        let name = sender.myname ?? "No Name"
        print("Hi there \(name)!")
    }
}
class CustomTapGestureRecognizer: UITapGestureRecognizer {
    var myname: String?
}

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.

UIButton

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.

NotificationCenter


import UIKit
let object = MyClass()
NotificationCenter.default.addObserver(object,
                                       selector: #selector(MyClass.sayHiTo),
                                       name: Notification.Name("MyNotificationName"),
                                       object: nil)
class MyClass {
    @objc
    func sayHiTo(sender: Notification) {
        print("Hi there!")
    }
}

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.

Moving on

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.

Automated E2E tests for your mobile app

Waldo provides the best-in-class runtime for all your mobile testing needs.
Get true E2E testing in minutes, not months.

Reproduce, capture, and share bugs fast!

Waldo Sessions helps mobile teams reproduce bugs, while compiling detailed bug reports in real time.