Messaging and Selectors

This section documents modern Objective-C as implemented by Apple Clang in the current Xcode release — Objective-C 2.0 plus ARC, literals and subscripting, instancetype, Clang modules, lightweight generics, nullability and @available — as published at Apple Developer Documentation and the Clang Objective-C specifications, which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against developer.apple.com before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Sending a message is not calling a function. [receiver doSomething] compiles into objc_msgSend(receiver, @selector(doSomething)), and the run time — not the compiler, not the linker — decides which code runs, by looking the selector up in the receiver’s class at the moment of the send. Every distinctive feature of Objective-C follows from that one fact.

Message-Send Syntax

[receiver message];                        // no arguments
[receiver messageWithArg:value];           // one argument
[receiver insertObject:obj atIndex:0];     // two arguments; selector is insertObject:atIndex:

[[Person alloc] initWithName:@"Ada"];      // nested: the result of alloc receives initWithName:

// Nesting is how expressions are built; the innermost brackets evaluate first.
NSString *upper = [[[person name] stringByTrimmingCharactersInSet:
                        [NSCharacterSet whitespaceCharacterSet]] uppercaseString];

Deep nesting quickly becomes unreadable. Break it with intermediate variables, or — for property access — with dot syntax, which is sugar for the same message send:

NSString *name = person.name;          // exactly [person name]
person.name    = @"Ada";               // exactly [person setName:@"Ada"]

Dot syntax is covered in Properties and Encapsulation.

Messages to nil

Sending any message to nil is legal, does nothing, and returns a zero value. This is the single most consequential difference from C++ or Java, where the equivalent is a crash.

Person *p = nil;

[p sayHello];                          // no-op, no crash
NSString *n     = [p name];            // nil
NSUInteger len  = [p count];           // 0
BOOL       flag = [p isReady];         // NO
double     d    = [p ratio];           // 0.0
NSRange    r    = [p range];           // {0, 0} -- struct is zeroed

The return value is zero for every type: nil for object pointers, 0 for integers and floats, and an all-zero struct. (On current Apple ABIs this holds for struct returns as well; on some older or non-Apple run-time configurations, struct and long double returns from nil were undefined, which is why very old code sometimes guards them explicitly.)

This is why idiomatic Objective-C carries so few null checks — most of the time "do nothing" is the right behaviour for a missing object:

// No guard needed: if delegate is nil, nothing happens.
[self.delegate operationDidFinish:self];

// But do notice what nil absorbs. This branch is taken both when the array is
// empty AND when it is nil, because [nil count] is 0:
if ([self.items count] == 0) { … }

The trap is silence: a message that mysteriously does nothing usually means an unexpectedly nil receiver somewhere upstream, and no error is reported at the point of failure. When a nil receiver would be a programming error, assert it (NSParameterAssert(receiver)).

Selectors

A selector is the run time’s interned, unique name for a method. It encodes the method’s name and argument count only — not its class, argument types or return type.

SEL sel  = @selector(insertObject:atIndex:);   // compile-time, checked for well-formedness
SEL sel2 = NSSelectorFromString(@"insertObject:atIndex:");  // run-time, from a string

NSString *name = NSStringFromSelector(sel);    // @"insertObject:atIndex:"
BOOL same = sel == sel2;                       // YES -- selectors are interned, compare with ==

Prefer @selector(…​) wherever the name is known at compile time: it is checked for syntactic validity and is visible to Xcode’s refactoring and to -Wundeclared-selector. Reserve NSSelectorFromString for genuinely dynamic cases (plug-in dispatch, names arriving from data).

Because a selector carries no type information, two same-named methods in unrelated classes share one selector and may have incompatible signatures. That is exactly what respondsToSelector: cannot protect you from — it answers "does this object implement a method by this name", not "with this signature".

respondsToSelector:

if ([self.delegate respondsToSelector:@selector(viewer:didSelectItem:)]) {
    [self.delegate viewer:self didSelectItem:item];
}

This is the standard idiom for calling an @optional protocol method and is discussed further in Protocols and Delegation. Note that a nil delegate answers NO, so the check subsumes the nil test.

performSelector:

performSelector: sends a message named at run time:

[obj performSelector:@selector(refresh)];
[obj performSelector:@selector(setName:) withObject:@"Ada"];
[obj performSelector:@selector(a:b:) withObject:x withObject:y];

// Deferred / cross-thread variants:
[obj performSelector:@selector(refresh) withObject:nil afterDelay:0.5];
[obj performSelectorOnMainThread:@selector(refresh) withObject:nil waitUntilDone:NO];

Three limits are worth knowing. It takes at most two arguments; those arguments and the return value must be objects (no int, no struct); and under ARC the compiler warns performSelector may cause a leak because its selector is unknown — it cannot see the selector’s memory-management family. For anything beyond a trivial dynamic call, prefer a block, or NSInvocation, or an explicit IMP call:

// NSInvocation: arbitrary signatures, including non-object arguments and returns.
NSMethodSignature *sig = [obj methodSignatureForSelector:sel];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
inv.target   = obj;
inv.selector = sel;
NSInteger arg = 42;
[inv setArgument:&arg atIndex:2];       // indexes 0 and 1 are self and _cmd
[inv invoke];

IMP: Calling the Implementation Directly

An IMP is the C function pointer behind a method: id (*)(id self, SEL _cmd, …). Fetching it once and calling it repeatedly skips the dispatch lookup, which occasionally matters in a measured hot loop:

SEL  sel = @selector(processValue:);
IMP  imp = [processor methodForSelector:sel];
void (*fn)(id, SEL, NSInteger) = (void (*)(id, SEL, NSInteger))imp;

for (NSInteger i = 0; i < 1000000; i++) {
    fn(processor, sel, i);              // no objc_msgSend per iteration
}

This is an optimisation of last resort: it freezes the implementation at the moment you fetched it, so subsequent swizzling, KVO or subclass overrides are bypassed. Measure before and after, or do not do it.

Dynamic Binding and Polymorphism

Because the selector is resolved against the receiver’s actual class at send time, polymorphism needs no virtual keyword and no declaration — it is simply how messaging works:

NSArray *shapes = @[ [Circle new], [Square new], [Triangle new] ];

for (Shape *shape in shapes) {
    [shape draw];       // each object runs its own draw, chosen at run time
}

The compiler used Shape * only to check that draw is a plausible message; the run time chose the implementation. Sending draw to an object that has none is not a compile error but a run-time one — unrecognized selector sent to instance, raised as an NSInvalidArgumentException — and even that is interceptable, as Dynamic Method Resolution and Forwarding describes.

How a Message Is Resolved

flowchart TD S["[receiver selector]
compiles to objc_msgSend(receiver, sel, ...)"] --> N{"receiver == nil?"} N -->|yes| Z["return 0 / nil / zeroed struct
(no work done)"] N -->|no| C{"selector in the
class's method cache?"} C -->|hit| I["call the cached IMP"] C -->|miss| L{"selector in the
class's method list?"} L -->|found| F["cache it, then call the IMP"] L -->|not found| U{"has a superclass?"} U -->|yes| L2["search the superclass's
method list, and so on up to NSObject"] L2 --> L U -->|no| R["message forwarding begins:
+resolveInstanceMethod:
then forwardingTargetForSelector:
then forwardInvocation:"] R --> D["if nothing handles it:
doesNotRecognizeSelector: raises
NSInvalidArgumentException"] style Z fill:#dbe9d5,stroke:#4a7a3a style I fill:#dce9f7,stroke:#2f6fa8 style F fill:#dce9f7,stroke:#2f6fa8 style R fill:#f6e9cf,stroke:#b08a34 style D fill:#e05252,stroke:#8a1f1f,color:#fff

The forwarding branch on the right is the subject of Dynamic Method Resolution and Forwarding; the cache and method-list machinery is detailed in The Objective-C Runtime.

Static and Dynamic Typing

A variable’s static type is what the compiler checks; the object’s dynamic type is what actually determines behaviour. Objective-C lets you choose how much static checking you want:

Person *typed   = [Person new];   // static type Person: unknown messages are a compile error
id      untyped = [Person new];   // static type id: any declared message compiles

With id, the compiler will accept any message that some visible @interface declares, and will warn only if the name is unknown everywhere. That flexibility is what makes collections and factory methods convenient, and it is also how typos reach run time. Prefer a concrete static type wherever you know it.

__kindof

__kindof Type * means "this type or any subclass of it" — it keeps the documentation value of a specific type while relaxing the casts that would otherwise be needed:

// Without __kindof, the caller must cast every element down from UIView *.
- (__kindof UIView *)viewWithTag:(NSInteger)tag;

MyButton *b = [container viewWithTag:7];   // compiles, no cast needed

It is a compile-time convenience only: nothing is verified at run time. See Lightweight Generics and Nullability.

Introspection

The run time can be asked about any object, which is what makes generic containers, serialisation and test doubles straightforward:

id obj = @"hello";

[obj class];                                   // the class object
[obj isKindOfClass:[NSString class]];          // YES -- this class OR a subclass
[obj isMemberOfClass:[NSString class]];        // exact class only (often NO for class clusters!)
[obj respondsToSelector:@selector(length)];    // YES
[obj conformsToProtocol:@protocol(NSCopying)]; // YES
[[obj class] superclass];                      // __NSCFString -- NOT NSObject: `obj` is a
                                               // class-cluster instance (see below)

NSStringFromClass([obj class]);                // a printable name
NSClassFromString(@"NSString");                // a Class from a name, or Nil
Test Use when

isKindOfClass:

Almost always — it respects inheritance.

isMemberOfClass:

Rarely; it excludes subclasses, and class clusters mean the concrete class is usually a private subclass such as __NSArrayI rather than NSArray itself.

respondsToSelector:

Before calling an optional or dynamically discovered method.

conformsToProtocol:

To check a capability contract rather than a concrete type — usually the better design.

Prefer asking about capability (respondsToSelector:, conformsToProtocol:) over asking about class: it survives refactoring, works with class clusters and proxies, and expresses what the code actually needs.

See Also