Selectors: SEL, @selector, and Method Names as First-Class Values
Understand precisely what a SEL value is — a unique runtime-registered identifier for a method name, not the method's code itself — and learn @selector syntax, NSStringFromSelector and NSSelectorFromString conversions, and why storing and comparing selectors as values is useful.
The previous lesson traced the full message-sending pipeline and named its second stage 'Selector' without fully unpacking what a selector actually is as a piece of data. This lesson does exactly that. A selector turns out to be a genuinely interesting kind of value in Objective-C: not a block of executable code, not a memory address pointing at a function, but a unique, runtime-registered identifier that names a method without saying anything at all about what that method does or how to find its code for any particular class. Understanding selectors as values you can store, compare, print, and convert to and from strings — separately from the act of actually sending a message — is what makes the next lesson's topic, performSelector, and the Runtime module's deeper coverage of dynamic dispatch both comprehensible.
What SEL Actually Is: An Identifier, Not the Method
SEL is the C-level type Objective-C uses to represent a selector as a value. The single most important thing to internalize about SEL is what it is not: it is not a function pointer, and it does not point at, or contain, any executable code at all. A SEL value is an opaque, runtime-registered identifier — essentially a unique name — that identifies which method is being referred to, entirely independent of any particular class's implementation of it.
This independence from any specific implementation is the whole point. The selector sayHello (as a SEL value) is exactly the same value whether it's used to send a message to a Person object, a Dog object, or any other class that happens to implement a method named sayHello — because the selector only names the method, it doesn't carry around a specific class's code for it. Finding the actual code that should run for a given selector, on a given receiver, is precisely the job of the method lookup stage covered in the previous lesson — a job that only makes sense because the selector itself carries no implementation, leaving that decision to be made fresh at runtime based on the receiver.
Because two selectors with the same method name are guaranteed by the runtime to be the exact same underlying value — the runtime keeps a registry ensuring uniqueness, so sayHello as a selector is registered exactly once, no matter how many classes implement a method with that name or how many times @selector(sayHello) appears in your source code — SEL values can be compared directly and cheaply, and that registry-backed uniqueness is exactly what makes fast method lookup and reliable equality checks on selectors possible.
• SEL — an opaque type representing a selector: a unique identifier for a method name, registered by the runtime. • A SEL value contains no executable code and is not a function pointer of any kind. • The same selector name (e.g. sayHello) is guaranteed to be one single, unique SEL value across the whole program, regardless of how many classes implement a method by that name. • Finding actual code for a given selector on a given receiver is the separate job of method lookup, not something the selector itself does.
@selector(...): Capturing a Selector Without Sending a Message
SEL selector = @selector(sayHello); is how you capture a selector as a value in your own code, entirely separately from actually sending a message. The @selector(...) syntax takes a bare method name — written exactly as it would appear inside a message send's brackets, colons and all for multi-argument selectors — and evaluates, at compile time, to the corresponding SEL value. Crucially, writing @selector(sayHello) does not call sayHello on anything; it only produces the identifier naming that method, ready to be stored, passed around, or compared.
Multi-argument selectors are captured the same way, with every colon-labeled part included: SEL setter = @selector(setName:age:); captures the single, two-part selector setName:age: as one SEL value, exactly matching how that same selector would be written inside an actual message send like [person setName:@"Ana" age:30]. This consistency is deliberate — @selector(...)'s argument is always written exactly as the selector would appear between the brackets of a real message send to that method, whether it takes zero, one, or several arguments.
It's worth being precise about the compile-time versus runtime distinction here: @selector(sayHello) is resolved by the compiler to a specific SEL value at compile time, based purely on the text sayHello you wrote — the compiler does not check, at this point, whether any class in your program actually implements a method named sayHello. That check (does the receiver's class actually know what to do with this selector) only happens later, if and when this captured SEL value is actually used to send a message to some receiver.
• @selector(sayHello) — captures a selector as a SEL value without sending any message. • @selector(setName:age:) — multi-argument selectors are written with every colon-labeled part, exactly as inside a real message send. • @selector(...) is resolved at compile time based on the text you write, not by checking whether any class actually implements that method. • Whether the captured selector is actually implemented by some receiver's class is only checked later, when it's used to send a message.
Converting Between Selectors and Strings
Because a SEL is fundamentally a name, Objective-C provides two functions for moving between a SEL value and its textual, human-readable form. NSStringFromSelector(SEL aSelector) takes a SEL and returns an NSString * spelling out its name — NSStringFromSelector(@selector(sayHello)) returns the string @"sayHello". This is genuinely useful for logging and debugging: printing which selector a piece of dynamic code is about to send, or including a selector's name in an error message, requires converting it to text first, since a raw SEL value isn't itself printable text.
NSSelectorFromString(NSString *selectorName) does the reverse: given a string, it returns the corresponding SEL value, registering it with the runtime if that exact selector name hasn't been seen before. SEL dynamic = NSSelectorFromString(@"sayHello"); produces exactly the same SEL value as @selector(sayHello) would — the two ways of obtaining that selector are fully interchangeable from that point forward.
The practical value of NSSelectorFromString is that it lets a selector be determined while the program is running, from data the compiler could never have seen in advance — a string read from a configuration file, built from user input, or received over a network. This is precisely the situation @selector(...) cannot handle, since its argument must be written literally in your source code and resolved at compile time; NSSelectorFromString is the tool for exactly the cases where the selector's name isn't known until runtime, and it's the natural companion to the performSelector: family of methods covered in the next lesson, which need a SEL value to actually invoke.
• NSStringFromSelector(@selector(sayHello)) returns @"sayHello" — converts a SEL to its readable name, useful for logging and debugging. • NSSelectorFromString(@"sayHello") returns the same SEL value as @selector(sayHello) — converts a string name to a SEL. • @selector(...) requires a literal, compile-time-known name; NSSelectorFromString accepts a name only known at runtime (from a file, user input, etc.). • NSSelectorFromString is the natural companion to performSelector:, which needs a SEL value to invoke dynamically.
Selectors as First-Class Values: Storing, Passing, and Comparing
Because a SEL is an ordinary value (not a special piece of syntax that only exists inside message-send brackets), it can be treated like any other value in your program: stored in a variable, stored as a property on an object, passed as an argument to a function or method, held in a collection, and returned from a method. SEL action = @selector(sayHello); followed by using action somewhere else entirely, possibly much later or in a different method, is a completely ordinary and common pattern.
This first-class quality is what makes possible a range of patterns that would otherwise require far more machinery: a button object that stores a SEL representing 'the action to perform when tapped,' decided by whoever configured the button rather than hard-coded into the button's own class; a dispatch table mapping string command names to SEL values, letting a program's behavior be driven by external, data-like configuration rather than a long chain of hard-coded if/else branches; or simply passing 'which method to call' as a parameter into a piece of reusable, generic code that doesn't itself know or care what specific behavior it's triggering.
Selectors can also be compared directly: two SEL values can be checked for equality (selector1 == selector2 or, more idiomatically, using the C function sel_isEqual), and because the runtime guarantees a given selector name maps to exactly one unique SEL value, this comparison is fast and reliable — you're not comparing strings character by character, you're comparing two occurrences of the runtime's single, canonical identifier for that method name.
• SEL values can be stored in variables and properties, passed as arguments, held in collections, and returned from methods — just like any other value. • This enables patterns like configurable button actions, string-to-selector dispatch tables, and passing 'which method' as a parameter to generic code. • SEL values can be compared for equality directly and cheaply, because the runtime guarantees one unique SEL per selector name. • None of this treats a selector as code — it's always just an identifier, used later to look up and invoke actual code on some receiver.
Preview: Why the Same Selector Can Mean Different Code on Different Receivers
It's worth closing this lesson by connecting selectors explicitly back to the previous lesson's method lookup stage, because it explains a fact about selectors that can otherwise seem strange: the exact same SEL value, say @selector(description), can be sent to two completely different objects — an NSString and a Person — and trigger two entirely different blocks of code, even though it's genuinely, literally the same selector value in both sends.
This is possible precisely because a selector, as established throughout this lesson, carries no implementation of its own — it's purely a name. When [someString description] and [somePerson description] are each sent, the runtime performs method lookup independently for each one, consulting NSString's class (or its superclasses) for the first, and Person's class (or its superclasses) for the second. Each lookup can land on a completely different implementation, because each receiver's class may define (or inherit) its own distinct code for handling that same selector name.
This is a preview, deliberately left incomplete here, of dynamic dispatch — the full mechanics of how a class defines a method implementation for a given selector, how subclasses can override an inherited one, and exactly how the runtime's lookup and caching work in detail, are covered fully later in the Runtime module. For now, the key idea to carry forward is this: a SEL value identifies which method is being asked for, but never which code will run — that second question is always answered separately, by the receiver, at the moment the message is actually sent.
• The same SEL value (e.g. @selector(description)) can trigger entirely different code depending on which object receives it. • This is possible because a selector carries no implementation — method lookup is performed fresh for each receiver's actual class. • This is the seed of dynamic dispatch, covered in full mechanical detail in the later Runtime module. • Takeaway: a SEL identifies which method is being asked for; the receiver, at send time, determines which code actually answers it.
Code Examples
helloSelector and setterSelector are captured at compile time via @selector(...), with the two-part selector setName:age: written exactly as it would appear in a real message send. NSStringFromSelector converts a SEL to readable text for logging. NSSelectorFromString does the reverse, building a SEL from a runtime string -- and because the runtime guarantees uniqueness per name, it produces a SEL equal to the one @selector(sayHello) would have produced, so the direct == comparison is valid and reliable.
BOOL sameSelector = (helloSelector == rebuiltSelector);
Button stores a SEL (self.action) as an ordinary property value, entirely decoupled from any specific method implementation -- Button's own code never needs to know what handleSave actually does. This is the first-class-value pattern this lesson describes: the selector is configured by other code, stored, and only later (via performSelector:, covered in the next lesson) would actually be used to send a message to the target.
saveButton.action = @selector(handleSave);
// Treating a SEL as if it were a function pointer you could call directly, like a C function pointer SEL fn = @selector(sayHello); fn(); // does not compile / does not mean what it looks like it means
SEL selector = @selector(sayHello); [person performSelector:selector]; // the selector is used to look up code on a specific receiver
A SEL is not a function pointer and cannot be invoked on its own; it carries no executable code at all. A selector only becomes 'code that runs' when it's paired with a receiver, via a message send or performSelector:, and the runtime performs method lookup.
// Assuming @selector(sayHello) checks whether any class actually implements sayHello SEL s = @selector(thisMethodDoesNotExistAnywhere); // compiles successfully
// Understanding that @selector(...) only captures a name at compile time, with no guarantee any class implements it
@selector(...) resolves purely from the text you write; the compiler does not verify that any class implements a method by that name. Whether some receiver's class actually responds to the selector is only discovered later, when the selector is used to send a message.
// Comparing selectors by converting both to strings and comparing text if ([NSStringFromSelector(sel1) isEqualToString:NSStringFromSelector(sel2)]) {... } if (sel1 == sel2) {... } // or sel_isEqual(sel1, sel2)
Because the runtime guarantees exactly one unique SEL value per selector name, two SEL values can be compared directly and cheaply. Converting both to strings first is unnecessary overhead and obscures the fact that SEL equality is already a fast, reliable, first-class comparison.
What does a SEL value actually represent? A. A pointer to the executable machine code for a specific method implementation B. A unique, runtime-registered identifier that names a method, independent of any specific class's implementation of it C. A copy of the entire method's source code, stored as a string D. A memory address where a specific object's instance variables are stored
B. A unique, runtime-registered identifier that names a method, independent of any specific class's implementation of it — SEL is an opaque type representing a selector -- a unique identifier for a method name registered by the runtime. It contains no executable code; it only names which method is being referred to, leaving the question of which actual code should run to be answered separately by method lookup on a specific receiver.
What is the key difference between using @selector(sayHello) and NSSelectorFromString(@"sayHello") to obtain a SEL? A. They produce entirely different, incompatible SEL values B. @selector(...) requires the name to be known at compile time; NSSelectorFromString accepts a name only known at runtime, but both yield the same SEL for the same name C. NSSelectorFromString can only be used with multi-argument selectors D. @selector(...) sends the message immediately, while NSSelectorFromString does not
B. @selector(...) requires the name to be known at compile time; NSSelectorFromString accepts a name only known at runtime, but both yield the same SEL for the same name — @selector(...) is resolved by the compiler from a literal name written in your source code. NSSelectorFromString accepts a string determined at runtime (from a file, user input, etc.) and returns the identical SEL value the runtime would associate with that same name.
Why can the exact same SEL value trigger completely different code when sent to two different objects? A. It can't -- the same SEL always triggers the same code regardless of receiver B. Because a selector carries no implementation of its own; method lookup is performed fresh for each receiver's actual class, which may define distinct code for the same selector name C. Because SEL values are randomly reassigned by the runtime on every message send D. Because @selector(...) generates a new, different SEL value each time it's written in source code
B. Because a selector carries no implementation of its own; method lookup is performed fresh for each receiver's actual class, which may define distinct code for the same selector name — A selector only names a method; it never carries code. When the same selector is sent to different receivers, method lookup runs independently for each one against that receiver's actual class, which is exactly why two receivers can implement the same selector name with entirely different behavior.
What does NSStringFromSelector(@selector(setName:age:)) return? A. The value nil, because multi-argument selectors cannot be converted to strings B. The string "setName:age:" C. Two separate strings, "setName:" and "age:" D. A compile-time error
B. The string "setName:age:" — NSStringFromSelector converts a SEL to its full readable name, including every colon-labeled part for a multi-argument selector -- setName:age: is one selector, and its string form preserves both parts exactly as written.
SEL helloSelector = @selector(sayHello);
SEL setterSelector = @selector(setName:age:);
// Converting a SEL to a readable string
NSString *name = NSStringFromSelector(helloSelector);
NSLog(@"Captured selector: %@", name); // "Captured selector: sayHello"
// The reverse: building a SEL from a string known only at runtime
NSString *dynamicName = @"sayHello"; // could come from a config file, etc.
SEL rebuiltSelector = NSSelectorFromString(dynamicName);
// The same selector name always yields the same, comparable SEL value
BOOL sameSelector = (helloSelector == rebuiltSelector); // YES@interface Button : NSObject
@property (nonatomic, assign) SEL action; // which selector to send when tapped
@property (nonatomic, weak) id target; // which object to send it to
@end
@implementation Button
- (void)simulateTap {
// The Button class doesn't know or care what "action" actually does --
// it only stores and later uses the selector as an identifier.
NSLog(@"Button tapped, would send %@ to target", NSStringFromSelector(self.action));
}
@end
// Configuring the button elsewhere, entirely separately from Button's own code:
Button *saveButton = [[Button alloc] init];
saveButton.action = @selector(handleSave);
saveButton.target = someController;