NSObject Fundamentals

Build a working, practical command of the core NSObject-level methods every Objective-C object inherits and that everyday code relies on constantly: equality and hashing, human-readable descriptions, class introspection, safe dynamic message checking, and dynamic invocation via performSelector: -- including exactly where each one's safety limits are and where a more powerful tool takes over.

Nearly every object in an Objective-C program, whatever else it is, is also an NSObject -- and NSObject supplies a small set of foundational methods that show up in essentially all Objective-C code, from simple debug logging to safe dynamic dispatch in loosely coupled frameworks. This lesson walks through that core method set as a practical toolkit: what each method does, when reasonably to reach for it, and, for a couple of them, exactly where their convenience runs out and a more capable but more verbose tool (NSInvocation, or a dedicated lesson) has to take over instead. Two of the methods covered here -- isEqual: and hash -- get only a brief, practical introduction in this lesson; the sibling Equality and Hashing addendum lesson covers both in full depth, including the equality/hash contract and the collection hazards that come with getting them wrong, and is the lesson to read next for that material.

isEqual: and hash: A Practical Preview

isEqual: is NSObject's method for content-based ('value') equality -- overriding it lets two distinct object instances be considered equal based on what they represent rather than whether they're literally the same object in memory. hash returns an NSUInteger the collection classes (NSSet, NSDictionary, and their mutable counterparts) use internally to bucket objects efficiently, and it must be kept consistent with isEqual: -- equal objects must always report equal hashes.

This lesson introduces both at a practical, everyday level: know that they exist, know roughly what they're for, and know that they must be overridden together, never just one. The full depth of that contract -- why it matters, a fully worked custom-class example, and the specific hazard of mutating an object after it's inserted into a hash-based collection -- is the entire subject of the sibling Equality and Hashing addendum lesson, which this one defers to rather than duplicating.

• isEqual: is NSObject's content/value-equality method -- override it to make two distinct instances comparable by what they represent. • hash returns the NSUInteger NSSet and NSDictionary use internally to bucket objects -- it must stay consistent with isEqual:. • Both must be overridden together, never just one -- see the sibling Equality and Hashing lesson for the full contract and its hazards.

description and debugDescription: A Practical Preview

description returns a human-readable NSString summarizing an object's state -- it's what NSLog(@"%@", obj) actually calls, and what an object prints as when logged directly, interpolated into a format string, or displayed by many debugging tools. The default NSObject implementation is minimally useful (typically just the class name and memory address), so overriding it with something that actually names an object's meaningful state is one of the most immediately useful small customizations a model class can make.

debugDescription serves a closely related purpose specifically for the debugger and for debugDescription-aware tooling like po in lldb -- by default it simply falls back to description, but a class can override it separately to show more verbose, debugging-oriented detail than would be appropriate for ordinary logging. Both methods are covered again, with worked custom-class examples, in a sibling addendum lesson dedicated specifically to them -- this lesson's job is only to establish what they're for.

• description returns a human-readable NSString -- it's what %@ and NSLog actually call to render an object as text. • The default NSObject description is minimally useful (class name plus address) -- overriding it is one of the highest-value small customizations for a model class. • debugDescription defaults to description but can be overridden separately for more verbose, debugger-specific detail (used by tools like lldb's po).

class and superclass: Basic Introspection

class returns an object's actual runtime class -- an id-typed Class object identifying exactly which class the receiver is an instance of. This is distinct from the compile-time static type a variable is declared with: a variable declared as NSObject * might, at runtime, actually hold an instance of some subclass, and [obj class] reports that real, dynamic class, not the declared pointer type.

superclass returns the Class object one level up the inheritance chain from whatever class is asked -- calling it on an instance's class, or chaining [[obj class] superclass], walks up toward the root class one step at a time. Both methods exist at the heart of how the runtime itself reasons about class hierarchies, and they're the same class-object machinery the Objective-C Class Anatomy and Objective-C Runtime lessons describe in more structural depth.

• class returns an object's actual runtime class -- which may differ from its variable's declared static type. • superclass returns the Class one level up the inheritance chain from whatever class it's called on. • Both operate on the same live class objects the runtime itself uses for method lookup and dispatch.

isKindOfClass: vs isMemberOfClass:: The Critical Distinction

isKindOfClass: answers 'is this object an instance of this class, or any subclass of it?' -- it walks the receiver's entire class hierarchy, checking the class itself and then every superclass up the chain, returning YES if a match is found anywhere along that walk. This is the check almost always wanted when the goal is 'can I safely treat this object as at least this type,' because it correctly accounts for subclassing: a UIButton instance answers YES to [button isKindOfClass:[UIControl class]], even though UIButton is not itself UIControl, because UIButton IS a kind of UIControl through inheritance.

isMemberOfClass: is strictly narrower: it answers 'is this object an instance of exactly this class, and no other' -- returning NO for any subclass, no matter how closely related. The same UIButton instance answers NO to [button isMemberOfClass:[UIControl class]], because a UIButton is not literally, exactly, a UIControl instance -- it's a subclass instance.

The practical rule is simple and worth internalizing precisely because getting it backwards causes real bugs: reach for isKindOfClass: whenever the question is really about capability or compatible behavior ('can I call UIControl methods on this'), and reserve isMemberOfClass: for the rarer case where subclasses genuinely must be excluded -- for example, code that must behave differently for a base class versus any of its specialized subclasses, such as strict archival or equality logic that intentionally treats subclass instances as a different kind of thing.

• isKindOfClass: checks the WHOLE class hierarchy -- YES for the exact class or any subclass of it. • isMemberOfClass: checks the EXACT class only -- NO for any subclass, however closely related. • Use isKindOfClass: for 'can I treat this as at least this type' checks; reserve isMemberOfClass: for the rarer case where subclasses must be explicitly excluded.

respondsToSelector: and conformsToProtocol:: Safe Dynamic Dispatch

respondsToSelector: answers, at runtime, whether a given object actually implements (or inherits) a method matching a given SEL, without invoking it -- it's the standard way to check before sending a message to an object whose exact class isn't statically known to be guaranteed to implement that method, such as an optional delegate method or a loosely typed id received from elsewhere in a program.

conformsToProtocol: answers the closely related but distinct question of whether an object's class declares conformance to an entire named protocol -- useful when code cares about a whole contract rather than checking individual selectors one at a time, though it's worth noting conformance is a declared, compile-time-checked claim about a class, not a per-method runtime guarantee the way respondsToSelector: is for any individual selector.

Both exist precisely because Objective-C's dynamism allows objects typed as id, or objects conforming only loosely to a protocol with optional methods, to arrive at a call site without the compiler being able to guarantee what they implement -- these two methods let code check safely before committing to a message send, rather than risking an 'unrecognized selector' crash.

• respondsToSelector: checks, at runtime, whether an object implements or inherits a specific method -- without calling it. • conformsToProtocol: checks whether an object's class declares conformance to an entire named protocol. • Both exist to let dynamically typed (id) or optionally-implemented code paths check safely before sending a message, avoiding an unrecognized-selector crash.

performSelector:: Safe Use and Real Limitations

performSelector: sends a message dynamically, by SEL rather than by ordinary bracket syntax, which is useful whenever the method to call is only known at runtime -- built from a string, chosen based on configuration, or supplied by calling code that only has a selector name. Paired with a respondsToSelector: check first, it's a safe, common pattern for optional or dynamically dispatched behavior.

Its most visible limitation is an ARC warning: the compiler cannot statically verify performSelector:'s return type or the ownership (retain/release) semantics of whatever it returns, because unlike ordinary bracket syntax, the selector being sent isn't known until runtime -- so ARC can't apply its usual, compile-time-inferred memory-management rules to the result. This is exactly why the compiler emits 'performSelector may cause a leak because its selector is unknown' -- it isn't a claim that a leak definitely happens, only that ARC has lost the static information it would normally use to manage the returned object's memory correctly, and the actual risk depends on what the invoked method's real memory-management convention turns out to be.

A second, structural limitation matters even more for correctness: performSelector: and its common variants only support a small, fixed number of arguments (typically up to two object arguments) and only work cleanly for methods returning an object or void -- a selector that needs more arguments, needs a non-object return type such as a struct or primitive with unusual size, or needs precise control over the call frame, cannot go through performSelector: at all. That's exactly the gap NSInvocation exists to fill, covered in its own dedicated lesson: it can represent and invoke an arbitrary method call, with any number of arguments and any return type, by working directly from the method's full type-encoding string rather than through performSelector:'s limited, fixed-shape convenience API.

• performSelector: sends a message dynamically by SEL -- useful when the method to call is only known at runtime. • ARC's 'may cause a leak because its selector is unknown' warning reflects lost static return-type/ownership information, not a guaranteed leak -- the real risk depends on the invoked method's actual memory-management convention. • performSelector: only supports a small, fixed number of object arguments and object/void returns. • Selectors needing more arguments or non-object returns need NSInvocation instead -- see the dedicated NSInvocation lesson.

Code Examples

The respondsToSelector: check happens first, confirming object actually implements -save before performSelector: ever tries to send it -- without that check, sending -save to an object that doesn't implement it would trigger an unrecognized-selector crash. This is the standard, safe pattern for calling an optional or dynamically determined method on an object whose exact class isn't statically guaranteed to implement it.

if ([object respondsToSelector:@selector(save)]) { Coordinate is equal by content (matching x and y), not by pointer identity -- and its hash is derived from the same fields isEqual: compares, which keeps the two consistent. This is only a preview of the pattern; the sibling Equality and Hashing lesson covers exactly why both must be overridden together and what breaks if they aren't.

• (NSUInteger)hash { The variable's compile-time type (NSObject *) constrains what the compiler will let you call on thing without a cast, but it has no effect on what [thing class] reports -- that reflects the object's true, dynamic identity, which is exactly the information isKindOfClass:, isMemberOfClass:, and the runtime's own dispatch machinery actually operate on.

NSLog(@"%@", [thing class]); // NSMutableString -- the actual runtime class isKindOfClass: walks the whole hierarchy and finds UIControl among UIButton's superclasses, so it answers YES. isMemberOfClass: only ever matches the exact class, and UIButton is not exactly UIControl, so it answers NO -- the two methods answer genuinely different questions and are not interchangeable.

BOOL kindCheck = [button isKindOfClass:[UIControl class]]; // YES

Rather than calling respondsToSelector: once per method in the Cacheable protocol, conformsToProtocol: checks the whole declared contract in a single call -- appropriate when code cares about an object's full conformance to a protocol rather than any one individual selector in isolation.

if ([item conformsToProtocol:@protocol(Cacheable)]) { This is exactly the gap the previous section describes: performSelector: has no variant that accepts three separate arguments, so a call like this one has to be built with NSInvocation instead, which works from the method's real type-encoding string (see the dedicated NSInvocation lesson) rather than a fixed, limited convenience signature.

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];

if ([object respondsToSelector:@selector(save)]) {
    [object performSelector:@selector(save)];
}
@interface Coordinate : NSObject
@property (nonatomic) NSInteger x;
@property (nonatomic) NSInteger y;
@end

@implementation Coordinate

- (BOOL)isEqual:(id)other {
    if (![other isKindOfClass:[Coordinate class]]) return NO;
    Coordinate *o = (Coordinate *)other;
    return self.x == o.x && self.y == o.y;
}

- (NSUInteger)hash {
    return self.x * 31 + self.y;
}

@end
NSObject *thing = [[NSMutableString alloc] init];

NSLog(@"%@", [thing class]);   // NSMutableString -- the actual runtime class
NSLog(@"%@", NSStringFromClass([thing class]));  // "NSMutableString"

// thing's DECLARED type is NSObject *, but [thing class] reports the
// REAL, dynamic class of the object it actually points to.
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];

BOOL kindCheck   = [button isKindOfClass:[UIControl class]];    // YES
BOOL memberCheck = [button isMemberOfClass:[UIControl class]];  // NO

// UIButton IS a kind of UIControl (through inheritance), but a UIButton
// instance is not literally, exactly, a UIControl instance.
@protocol Cacheable <NSObject>
- (NSData *)serialize;
- (void)restoreFromData:(NSData *)data;
@end

id item = someDynamicallyProvidedObject;

if ([item conformsToProtocol:@protocol(Cacheable)]) {
    NSData *data = [item serialize];
    // safe to treat item as fully Cacheable from here on
}
// performSelector: only cleanly supports up to two object arguments and
// an object/void return -- this method needs THREE arguments, so
// performSelector: cannot express the call at all:
//   - (void)recordEvent:(NSString *)name value:(NSInteger)value tag:(NSInteger)tag;

NSMethodSignature *sig =
    [recorder methodSignatureForSelector:@selector(recordEvent:value:tag:)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setSelector:@selector(recordEvent:value:tag:)];
[invocation setTarget:recorder];
NSString *name = @"launch";
NSInteger value = 42, tag = 7;
[invocation setArgument:&name atIndex:2];
[invocation setArgument:&value atIndex:3];
[invocation setArgument:&tag atIndex:4];
[invocation invoke];

Where These Methods Actually Show Up

isKindOfClass: and respondsToSelector: are the backbone of loosely-coupled Objective-C patterns: delegate and data-source protocols routinely mark methods @optional and call respondsToSelector: before invoking them, and code that handles a mix of object types from a collection typed id or NSObject * commonly branches on isKindOfClass: to decide how to treat each one. description is overridden almost universally on model classes specifically because it makes NSLog-based debugging dramatically more useful without needing a debugger at all. performSelector: shows up in dynamic, configuration-driven code -- calling a method whose name comes from a string, a plist, or reflection-style tooling -- and its limitations are exactly why NSInvocation exists as the more capable fallback the moment an argument list or return type doesn't fit performSelector:'s narrow shape.

// Sending -save without checking first: [object performSelector:@selector(save)];

if ([object respondsToSelector:@selector(save)]) { [object performSelector:@selector(save)]; }

Without the respondsToSelector: check, sending -save to an object that doesn't implement it (or inherit an implementation of it) crashes with 'unrecognized selector sent to instance' -- performSelector: does not make a message send any safer than ordinary bracket syntax on its own; the safety comes entirely from checking first.

What is the key difference between isKindOfClass: and isMemberOfClass:? A. They are interchangeable synonyms for the same check B. isKindOfClass: checks the object's entire class hierarchy (exact class or any subclass); isMemberOfClass: matches only the exact class, excluding all subclasses C. isMemberOfClass: checks the entire hierarchy; isKindOfClass: only matches the exact class D. isKindOfClass: only works on NSObject subclasses, while isMemberOfClass: works on any type

B. isKindOfClass: checks the object's entire class hierarchy (exact class or any subclass); isMemberOfClass: matches only the exact class, excluding all subclasses — isKindOfClass: walks the whole inheritance chain and returns YES for the exact class or any subclass; isMemberOfClass: only returns YES for an instance of that exact class, never a subclass.

What does respondsToSelector: actually check? A. Whether calling the method would succeed without crashing the entire app B. Whether the receiver implements or inherits a method matching the given selector, without invoking it C. Whether the selector's method has ever actually been called before D. Whether the selector is valid Objective-C syntax

B. Whether the receiver implements or inherits a method matching the given selector, without invoking it — respondsToSelector: is a pure introspection check -- it reports whether the object implements or inherits a matching method, without sending any message or running any code.

Why does ARC emit a warning like 'performSelector may cause a leak because its selector is unknown'? A. Because performSelector: always leaks memory in every case B. Because, unlike ordinary bracket syntax, the selector isn't known until runtime, so ARC cannot statically infer the returned object's ownership/memory-management convention C. Because performSelector: is deprecated and about to be removed from the language D. Because the warning is purely stylistic and has no relation to memory management

B. Because, unlike ordinary bracket syntax, the selector isn't known until runtime, so ARC cannot statically infer the returned object's ownership/memory-management convention — ARC relies on compile-time knowledge of a method's naming convention and return type to manage memory correctly. Since performSelector:'s target method is only resolved at runtime, ARC loses that static information and warns that it cannot guarantee correct memory management for the result -- not that a leak is certain.

Why can't performSelector: be used to call a method that takes three separate arguments? A. performSelector: has no inherent argument-count limit -- any method can be called through it B. performSelector: and its common variants only support a small, fixed number of object arguments (typically up to two) -- a three-argument call needs NSInvocation instead C. Three-argument methods cannot be represented in Objective-C at all D. performSelector: requires the method to take exactly zero arguments

B. performSelector: and its common variants only support a small, fixed number of object arguments (typically up to two) -- a three-argument call needs NSInvocation instead — performSelector:'s convenience variants top out at two object arguments. A method needing more arguments, or a non-object return type, requires building an NSInvocation from the method's real signature instead, since NSInvocation has no such fixed limit.

Why must isEqual: and hash always be overridden together, according to this lesson's preview (with the full explanation in the sibling Equality and Hashing lesson)? A. They don't need to be overridden together -- overriding either one alone is always safe B. hash must stay consistent with isEqual: because NSSet and NSDictionary rely on it for correct bucketing -- overriding only one breaks that consistency C. hash is deprecated in favor of isEqual: and should never be overridden at all D. The compiler automatically generates hash from isEqual: so overriding hash separately has no effect

B. hash must stay consistent with isEqual: because NSSet and NSDictionary rely on it for correct bucketing -- overriding only one breaks that consistency — hash-based collections depend on equal objects reporting equal hashes. Overriding isEqual: alone (or hash alone) breaks that consistency, which is exactly why this lesson previews the pairing and defers the full explanation to the sibling Equality and Hashing lesson.

Write a category on NSObject with a method -safeInvokeOptional: that takes a SEL, checks with respondsToSelector: whether self implements it, calls it with performSelector: if so, and otherwise logs that the selector isn't implemented. Then explain, in a comment, why this helper could not be extended to safely support a selector taking three separate arguments without switching to NSInvocation.

@implementation NSObject (SafeInvoke)\n- (void)safeInvokeOptional:(SEL)selector {\n if ([self respondsToSelector:selector]) {\n [self performSelector:selector];\n } else {\n NSLog(@"%@ does not implement %@", self, NSStringFromSelector(selector));\n }\n}\n@end\n\n// This could not be extended to a three-argument selector because\n// performSelector: (and its withObject: variants) only ever accept up\n// to two object arguments -- there is no performSelector:withObject:\n// withObject:withObject: form. A three-argument call needs an\n// NSMethodSignature and NSInvocation built from the real selector,\n// which can represent any argument count and any return type.

respondsToSelector: takes the SEL directly; performSelector: with no arguments is the simplest form to call once the check passes. The three-argument limitation is structural to performSelector:'s API shape, not something a wrapper method can work around.