Messages, not function calls
Unlike C++ or Java, where calling a method is (mostly) resolved to a fixed function address at compile time, Objective-C method calls are messages sent to a receiver via the Objective-C runtime's objc_msgSend function. Writing [receiver doSomething:argument] doesn't directly jump to a function; it compiles down to something like objc_msgSend(receiver, @selector(doSomething:), argument), which looks up the correct implementation at runtime based on the receiver's actual class.
Cricket analogy: Sending a message to a receiver is like a captain shouting a fielding instruction to 'point' rather than directly grabbing that fielder's legs — the instruction is dispatched, and it's up to that specific fielder to interpret and execute it.
This dynamic dispatch is what makes it valid to send a message to nil in Objective-C without crashing: [nilObject doSomething] simply returns nil (or zero/NULL for scalar return types), because objc_msgSend checks the receiver first and returns immediately if it's nil, rather than dereferencing a null pointer as C++ would. This behavior is frequently exploited to avoid verbose nil checks, though it can also mask bugs where a value silently becomes nil earlier than expected.
Cricket analogy: Sending an instruction to an empty slip cordon (no fielder there) simply results in nothing happening rather than the game crashing — the same graceful no-op as messaging nil in Objective-C.
Selectors and method signatures
A selector, represented by the SEL type, is essentially the name of a method — for example @selector(setName:age:) — used by the runtime to look up the actual implementation (an IMP, a function pointer) at message-send time. Selectors are interned strings, meaning identical selector names always compare equal, which lets you check whether an object responds to a message with respondsToSelector: before invoking it dynamically via performSelector: or NSInvocation for more complex argument lists.
Cricket analogy: A selector is like a fielding position name such as 'gully' — the name itself is fixed and interned across every match, but which actual fielder stands there (the implementation) can change from game to game.
Dot notation vs bracket notation
Dot notation, such as person.name = @"Ada";, is pure syntactic sugar over message sends and is generally reserved for property access (getters/setters generated by @property), compiling to [person setName:@"Ada"]; behind the scenes. Bracket notation, [person setName:@"Ada"], is required for any method that isn't a property accessor, and many teams enforce a style rule of using dot notation only for properties to keep the distinction between 'accessing state' and 'sending a behavioral message' visually clear.
Cricket analogy: Checking .score on a scoreboard app is like glancing at a fixed display panel for a property, while calling [scoreboard incrementRuns:4] is like actively signaling the scorer to perform an action — two different kinds of interaction with the same object.
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
- (void)greetWithGreeting:(NSString *)greeting;
@end
@implementation Person
- (void)greetWithGreeting:(NSString *)greeting {
NSLog(@"%@, %@!", greeting, self.name);
}
@end
// Usage
Person *ada = [[Person alloc] init];
ada.name = @"Ada"; // dot notation -> setName:
[ada greetWithGreeting:@"Hello"]; // bracket notation, message send
SEL greetSelector = @selector(greetWithGreeting:);
if ([ada respondsToSelector:greetSelector]) {
[ada performSelector:greetSelector withObject:@"Hi"];
}
// Message to nil is safe and returns nil
Person *missing = nil;
NSString *result = [missing name]; // result is nil, no crashperformSelector: and its variants don't let ARC verify memory-management semantics at compile time, since the compiler can't know what the selector returns; this can lead to retain/release mismatches. Modern Objective-C code often prefers blocks or direct message sends where possible, reserving performSelector: for genuinely dynamic cases.
- Objective-C method calls compile down to objc_msgSend, a dynamic dispatch mechanism, not direct function calls.
- Sending any message to nil safely returns nil/0/NULL instead of crashing.
- A SEL (selector) is an interned name for a method, resolved to an IMP (implementation) at runtime.
- respondsToSelector: lets you check dynamically whether an object implements a given method.
- Dot notation is sugar for property accessor message sends (getters/setters).
- Bracket notation is required for any non-property method call.
- performSelector: bypasses some ARC compile-time safety checks and should be used sparingly.
Practice what you learned
1. What actually happens at runtime when you write [receiver doSomething]?
2. What is the result of sending a message to a nil object in Objective-C?
3. What does SEL represent in Objective-C?
4. When is dot notation appropriate in idiomatic Objective-C style?
5. What is a downside of using performSelector: for dynamic method invocation?
Was this page helpful?
You May Also Like
Class Methods vs Instance Methods
Learn the difference between Objective-C's + class methods and - instance methods, and when to use each in real-world class design.
Blocks in Objective-C
Learn how Objective-C blocks capture surrounding variables as closures, how __block and __weak change capture behavior, and how to avoid retain cycles.
Conditionals in Objective-C
Learn how Objective-C branches program flow using if/else, the ternary operator, and switch statements, and how BOOL and nil interact with truthiness.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics