Methods: Instance vs. Class, Signatures, Named Parameters, and Selectors

Write correct instance and class method declarations and definitions, understand every part of a method signature including nullability, construct multi-part named-parameter methods and explain exactly how their selector is built, and apply Objective-C's naming and visibility conventions correctly.

Every piece of behavior an Objective-C object exposes — from -description to +alloc to a custom method you write yourself — is a method, and by this point in the course you've been reading and calling dozens of them without necessarily having pulled apart every piece of their anatomy in one place. This lesson does exactly that: the full shape of a method declaration, the instance-versus-class distinction stated precisely, and Objective-C's most immediately unusual feature to newcomers — methods whose names are built from multiple labeled parts, interleaved with their parameters, rather than a single bare identifier followed by a parenthesized argument list the way C, Java, or Python write them. Understanding exactly how a name like sendMessage:toUser: becomes the method's real identity, its selector, is essential to reading Objective-C code fluently and to writing methods that read naturally at their call sites.

Instance Methods (-) vs. Class Methods (+): A Precise Restatement

A method declaration begins with either - or +, and this single character decides everything about who the method is sent to. - declares an instance method: it is only callable by sending a message to a specific, already-existing instance — [person description], where person is one particular object with its own ivars and its own state. + declares a class method: it is callable by sending a message to the class itself, using the class name as the receiver — [Person personWithName:@"Alice" age:30], where Person the type name stands in the receiver position, referring to the real class object that exists at runtime.

The deciding question for which prefix a method needs is always the same: does the method's logic need to read or write a specific instance's own ivars? If yes, it must be an instance method, because that per-object storage only exists once an instance has actually been allocated. If the method's job concerns the type as a whole — creating a new instance, reporting something true of every instance, computing a result purely from its parameters — a class method is the more honest, and often more useful, choice.

Both kinds of method are written with the same overall grammar, differing only in that leading symbol: return type in parentheses, then the method name (possibly multi-part, covered below), with each parameter's type also in parentheses immediately before its name. The +/- prefix must match exactly between a method's declaration (in @interface) and its definition (in @implementation) — a mismatch produces two entirely unrelated methods rather than one, silently, with no error pointing directly at the real mistake.

• - prefix: instance method, sent to a specific already-existing object. • + prefix: class method, sent to the class itself (the runtime class object). • Decide by asking: does the method need a specific instance's own ivars? Yes → instance method. No → candidate class method. • The +/- prefix must match exactly between @interface declaration and @implementation definition.

Anatomy of a Method Signature

A full method signature has a fixed shape you can decompose piece by piece. Take - (void)sendMessage:(NSString *)message toUser:(NSString *)user;. First comes the prefix, -, marking this as an instance method. Next, in parentheses, the return type: (void), meaning this method sends its message but hands nothing back to the caller. Then the method's name begins — but rather than one bare identifier, it is split into labeled parts, each one immediately followed by a colon, a parameter type in parentheses, and a parameter name: sendMessage:(NSString *)message, then toUser:(NSString *)user. The whole declaration ends with a semicolon (in @interface; in @implementation a { ... } body replaces it).

Every parameter in that signature carries its own explicit type in parentheses — (NSString *)message means the parameter named message, inside the method body, is a pointer to an NSString. This is exactly analogous to how a C function parameter is typed, just written in a different position relative to the parameter's name: C writes NSString *message, Objective-C's method-parameter syntax writes (NSString *)message, the type still coming first, just parenthesized.

The return type follows the same parenthesized convention as parameter types, and it can be void (nothing returned), any object pointer type (NSString *, Person *), a scalar (BOOL, NSInteger, float), or the special instancetype, which Class Anatomy and the Class Methods lesson both used for initializers and factory methods specifically because it correctly returns 'an instance of whatever class this method was actually called on' — important once subclasses are involved, which the Inheritance section of this course covers directly next.

• Shape: -/+ prefix, then (returnType), then one or more label:(paramType)paramName parts, ending in ; (declaration) or { } (definition). • Every parameter has an explicit parenthesized type immediately before its name, e.g. (NSString *)message. • Return type is also parenthesized: (void), (BOOL), (NSInteger), an object pointer type, or instancetype. • instancetype is the conventional return type for initializers and factory methods, returning correctly-typed instances even from subclasses.

Multiple Named Parameters and Selector Construction

The single most distinctive thing about Objective-C method names, compared to almost any other mainstream language, is that a method with several parameters does not have one bare name followed by a comma-separated argument list — it has several labeled parts, each one directly adjacent to the parameter it introduces, which is meant to make a call site read close to a sentence of English. Consider the example this course uses as its canonical illustration:

``objc - (void)sendMessage:(NSString *)message toUser:(NSString *)user; ``

This declares an instance method with two named parts: sendMessage: labels the first parameter (message), and toUser: labels the second (user). Calling it looks like [messenger sendMessage:@"Hi there" toUser:@"Alice"] — read left to right, it genuinely reads as a sentence: 'send message hi-there to user alice', with each argument sitting immediately after the label that names its role, rather than positionally in a list where you'd have to remember which position means what.

The method's true, complete identity — the thing the runtime actually uses for lookup, and the thing you pass to @selector(...) or respondsToSelector: — is not sendMessage alone and not toUser alone; it is the concatenation of every label including its colon, in order, with no spaces: sendMessage:toUser:. This single, combined string is called the selector, and it is what identifies the method as one indivisible unit to the runtime. A method with three named parts, like - (void)insertObject:(id)object atIndex:(NSUInteger)index withPriority:(NSInteger)priority;, has the selector insertObject:atIndex:withPriority: — again, every label's colon included, concatenated in declared order, forming one selector rather than three separate ones.

It follows directly from this that changing the order of the labeled parts, or adding or removing one, produces a genuinely different selector and therefore a genuinely different method as far as the runtime is concerned — sendMessage:toUser: and toUser:sendMessage: (if such a method existed) would be two completely unrelated selectors, even though they might take the exact same two parameter types. This is also why a method with zero parameters, like -description, has a selector that is simply its bare name with no colon at all (description), while a method with even one parameter always has at least one colon in its selector, e.g. -setName: for a single-parameter setter.

• A multi-part method name has several label: segments, each immediately followed by that parameter's type and name. • The selector is every label (including its colon) concatenated in declared order, with no spaces: sendMessage:toUser: for the sendMessage:/toUser: example. • The selector is the method's true identity for the runtime — used for lookup, @selector(...), and respondsToSelector:. • Reordering, adding, or removing a labeled part changes the selector, and therefore identifies a different method entirely. • Zero-parameter methods (like -description) have a selector with no colon at all; every parameter adds exactly one colon.

Nullability Annotations

Because Objective-C object pointers can hold nil, and because nil is frequently a legitimate, expected value in one place but a bug everywhere else, method signatures commonly carry nullability annotations that document — and let the compiler warn about — whether a given parameter or return value is allowed to be nil. nullable marks a pointer type as one that may legitimately be nil: - (nullable NSString *)nicknameForUser:(NSString *)user; documents that this method might genuinely return nil (perhaps the user has no nickname set), and callers are expected to handle that possibility rather than assume a value is always present.

nonnull marks the opposite: a promise, checked by the compiler with warnings when it can detect a violation, that this pointer should never actually be nil. - (void)sendMessage:(nonnull NSString *)message toUser:(nonnull NSString *)user; documents that passing nil for either parameter is a misuse the compiler will flag. Many real headers avoid repeating nonnull on every single parameter by wrapping a whole block of declarations in NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END, which makes every pointer inside that block nonnull by default unless individually marked nullable — a convenience this course's dedicated Nullability module covers in full.

Nullability annotations do not change what actually happens at runtime — sending nil to a nonnull-annotated parameter still compiles and still runs (Objective-C famously tolerates messages sent to nil, simply doing nothing and returning a zeroed value, rather than crashing the way many other languages would). What nullability buys you is compiler-checked documentation: clearer method signatures, and warnings — not silent bugs — when code violates what a signature promises. This matters directly for method design: a well-written method signature communicates its nil-handling contract as part of the signature itself, not merely in a comment.

• nullable: documents that a pointer parameter or return value may legitimately be nil; callers should handle that case. • nonnull: documents that a pointer should never be nil; the compiler warns (not errors) on violations it can detect. • NS_ASSUME_NONNULL_BEGIN/END makes every pointer in a block nonnull by default, reducing repetition — full treatment in the Nullability module. • Nullability annotations are compiler-checked documentation, not a runtime enforcement mechanism — sending nil to a nonnull parameter still compiles and runs.

Declaration in @interface vs. Definition in @implementation

Every method you intend to be callable from outside the class (or, in practice, that you simply want documented for readability) is declared in the class's @interface, in its .h file: just the signature, ending in a semicolon, with no body — - (void)sendMessage:(NSString *)message toUser:(NSString *)user;. This declaration is a promise about the method's shape: its name (selector), its parameter types, and its return type, letting any file that imports the header know this method exists and exactly how to call it, without needing to see how it actually works.

The method's real logic — its definition — lives in the class's @implementation, conventionally in the matching .m file, repeating the exact same signature but replacing the trailing semicolon with a { ... } body containing the actual code: - (void)sendMessage:(NSString *)message toUser:(NSString *)user { NSLog(@"%@ -> %@: %@", self.currentUser, user, message); }. The signature in @implementation must match the one declared in @interface exactly — same prefix, same selector (same labels, same order), same parameter types, same return type — or the compiler treats them as unrelated, exactly as covered earlier for a mismatched +/- prefix.

This two-part split — a public shape in the header, a hidden body in the implementation file — is the same interface/implementation separation this course's earlier Interface and Implementation lesson established for properties and the class as a whole; methods simply follow the identical pattern. It's what allows other code to call your methods correctly by only ever reading the .h file, while the actual logic, and any private helper methods you don't want to expose at all (declared instead in a class extension, per the Class Extensions module), stays out of the public interface entirely.

• Declaration (in @interface, typically .h): signature only, ending in ; — no body, just the method's public shape. • Definition (in @implementation, typically .m): the same signature, { } body replacing the semicolon, containing the real logic. • The signature must match exactly between declaration and definition — prefix, selector, parameter types, return type. • This mirrors the same interface/implementation separation used for the class and its properties: public shape in the header, logic hidden in the .m file.

Overriding, Naming, and Visibility Conventions

A subclass overrides an inherited method by declaring and implementing a method with the exact same selector as one its superclass already defines — the full mechanics of overriding, and calling the overridden version via super, belong to the Inheritance section that follows this lesson, but the naming rule itself is simple: matching selector, matching parameter types, matching return type (or a covariant/compatible one) is what makes it an override rather than an unrelated new method that merely happens to share part of a name.

Objective-C method naming carries its own strong conventions, distinct from property naming. Method names, including every labeled part, are written in lowerCamelCase, starting with a lowercase letter — sendMessage:toUser:, respondsToSelector:, initWithName:age: — never capitalized at the start the way class names are (Person, NSString). Each labeled part should itself read as a meaningful word or short phrase describing exactly what that argument represents — toUser: tells you far more at the call site than a generic label like withValue: would for the same parameter.

For visibility, the working rule this course has used throughout Class Anatomy, Properties, and Instance Variables carries over directly to methods: anything meant to be part of a class's genuine public API belongs declared in the .h file's @interface, while methods meant only for the class's own internal use — helpers that outside code should never call directly — belong declared instead inside a class extension in the .m file (the Class Extensions module covers this construct in full), where they remain completely invisible to any file that only imports the public header. There is no private/public keyword on individual methods in Objective-C the way some other languages provide; visibility is entirely a matter of which file the declaration physically lives in.

• Overriding: a subclass declares a method with the exact same selector, parameter types, and (compatible) return type as one already defined by its superclass. • Method names use lowerCamelCase throughout, including every labeled part — never starting with a capital letter. • Each labeled part should read as a meaningful description of its parameter's role (toUser: vs. a vague withValue:). • Visibility convention: public API methods declared in the .h @interface; internal-only helper methods declared in a class extension in the .m file — there is no private/public keyword on an individual method.

Multi-Part Method Declaration

Each labeled part ends in a colon, immediately followed by that parameter's parenthesized type and its name. The selector is every label, colon included, concatenated in order with no spaces. A zero-parameter method has no colon at all in its selector, e.g. -description.

- (returnType)firstLabel:(paramType)paramName
           secondLabel:(paramType)paramName
            thirdLabel:(paramType)paramName;

Code Examples

This declares an instance method (-) returning nothing (void), with two labeled parts: sendMessage: introduces the message parameter, toUser: introduces the user parameter. Its selector -- the method's true, complete identity to the runtime -- is the concatenation of both labels including their colons: sendMessage:toUser:. Calling it looks like [messenger sendMessage:@"Hi" toUser:@"Alice"], reading left to right as a natural sentence.

sendMessage:toUser: needs self.currentUser, an instance's own data, so it must be an instance method (-), sent to a specific Messenger (bob). messengerForUser: creates and returns a new instance, and never touches any existing instance's data before it exists, so it is correctly a class method (+), sent to Messenger itself. Note both are declared in @interface with a semicolon and defined in @implementation with a matching signature and a body.

The parameter user is annotated nonnull -- passing nil for it is documented as a misuse the compiler can warn about. The return type is annotated nullable -- this method may genuinely return nil when no nickname is on file, and callers reading the signature know to handle that case rather than assuming a value is always present.

// @interface - (void)sendMessage:(NSString *)message toUser:(NSString *)user; // @implementation + (void)sendMessage:(NSString *)message toUser:(NSString *)user { ... }

// @interface - (void)sendMessage:(NSString *)message toUser:(NSString *)user; // @implementation - (void)sendMessage:(NSString *)message toUser:(NSString *)user { ... }

The +/- prefix must match exactly between declaration and definition. Declaring an instance method but defining it as a class method (or vice versa) produces two unrelated methods: the declared one has no implementation and fails at the call site, while the accidental one goes unused.

// treating sendMessage and toUser as two separate method names [messenger sendMessage:@"Hi"]; [messenger toUser:@"Alice"];

[messenger sendMessage:@"Hi" toUser:@"Alice"]; // one method, selector sendMessage:toUser:

sendMessage: and toUser: are not two separate methods -- they are two labeled parts of one single method whose selector is the full concatenation sendMessage:toUser:. Both arguments must be supplied together in one message send, in the declared order.

• (void)withPriority:(NSInteger)priority insertObject:(id)object; // reordering parts changes the selector - (void)insertObject:(id)object withPriority:(NSInteger)priority; // keep parts in the order callers expect

Because the selector is built by concatenating labels in declared order, swapping the order of labeled parts produces a genuinely different selector (withPriority:insertObject: vs. insertObject:withPriority:) -- not the same method reordered, but an entirely different, unrelated one from the runtime's point of view.

What is the selector for - (void)sendMessage:(NSString *)message toUser:(NSString *)user;? A. sendMessage B. sendMessage:toUser: C. sendMessagetoUser D. message:user:

B. sendMessage:toUser: — The selector is every labeled part, including its colon, concatenated in declared order with no spaces: sendMessage: followed by toUser: gives sendMessage:toUser:, the method's complete, true identity to the runtime.

What decides whether a method should be declared with - or +? A. Whether the method returns void or not B. Whether the method needs a specific instance's own ivars (instance method, -) or concerns the type as a whole (class method, +) C. Whether the method has any parameters at all D. Alphabetical order of the method name

B. Whether the method needs a specific instance's own ivars (instance method, -) or concerns the type as a whole (class method, +) — A method that reads or writes a specific object's own state must be an instance method (-), since that storage only exists per-instance. A method whose logic doesn't depend on any one instance's data is a natural candidate for a class method (+).

What does a nullable annotation on a method's return type mean? A. The method cannot be called from outside the class B. The method may legitimately return nil, and callers should be prepared to handle that C. The method always returns nil D. The method's return type will be inferred automatically by the compiler

B. The method may legitimately return nil, and callers should be prepared to handle that — nullable documents (and lets the compiler check) that a pointer -- here, the return value -- may genuinely be nil in some cases, signaling to callers that they should not assume a value is always present.

Where should a method meant only for a class's internal use, never called from outside code, be declared? A. In the public @interface in the .h file, marked with a private keyword B. In a class extension in the .m file, invisible to any file that only imports the public header C. It cannot be hidden at all in Objective-C D. As a C function instead of a method

B. In a class extension in the .m file, invisible to any file that only imports the public header — Objective-C has no private/public keyword on individual methods. Visibility is a matter of which file the declaration lives in: public API methods go in the .h @interface, while internal-only helpers go in a class extension in the .m file, where outside code can't see them.

How does declaring a method in @interface differ from defining it in @implementation? A. There is no difference; they're the same thing B. @interface has just the signature ending in a semicolon (no body); @implementation repeats the exact same signature with a { } body containing the real logic C. @interface contains the method body; @implementation only lists the method's name D. Methods cannot be declared in @interface at all

B. @interface has just the signature ending in a semicolon (no body); @implementation repeats the exact same signature with a { } body containing the real logic — A declaration in @interface is a promise about a method's shape (selector, parameter types, return type) with no logic. The matching definition in @implementation must repeat that exact signature, replacing the semicolon with a { } body containing the actual implementation.

Why does reordering the labeled parts of a method (e.g. from insertObject:withPriority: to withPriority:insertObject:) matter? A. It doesn't matter at all -- both refer to the same method B. It produces a genuinely different selector, and therefore an entirely different, unrelated method as far as the runtime is concerned C. It only changes the method's return type D. It automatically converts the method into a class method

B. It produces a genuinely different selector, and therefore an entirely different, unrelated method as far as the runtime is concerned — Since the selector is the concatenation of labels in declared order, changing that order builds a different string entirely -- insertObject:withPriority: and withPriority:insertObject: are two distinct selectors, identifying two unrelated methods even if their parameter types happened to match.

- (void)sendMessage:(NSString *)message
             toUser:(NSString *)user;
// Messenger.h
@interface Messenger : NSObject
@property (nonatomic, copy) NSString *currentUser;

- (void)sendMessage:(NSString *)message toUser:(NSString *)user;   // instance method
+ (instancetype)messengerForUser:(NSString *)user;                  // class method (factory)
@end

// Messenger.m
@implementation Messenger

- (void)sendMessage:(NSString *)message toUser:(NSString *)user {
    NSLog(@"%@ -> %@: %@", self.currentUser, user, message);
}

+ (instancetype)messengerForUser:(NSString *)user {
    Messenger *m = [[self alloc] init];
    m.currentUser = user;
    return m;
}

@end

// Calling code:
Messenger *bob = [Messenger messengerForUser:@"Bob"];   // class method, sent to Messenger
[bob sendMessage:@"Hey Alice" toUser:@"Alice"];           // instance method, sent to bob
- (nullable NSString *)nicknameForUser:(nonnull NSString *)user;

- (nullable NSString *)nicknameForUser:(NSString *)user {
    return self.nicknames[user];   // may legitimately be nil if user has no nickname
}