Class Extensions: Private Properties, Private Methods, and the Anonymous Category

Understand the class-extension syntax @interface ClassName (), use it as the standard place to declare private properties, methods, and ivars, and explain precisely how a class extension differs from a named category.

So far, every property and method this course has declared on Person has lived in the public @interface Person : NSObject { ... } @end block at the top of Person.h — visible to any file that imports the header. But real classes almost always need internal state and helper methods that are none of the outside world's business: a cache, a flag tracking whether setup has run, a helper method that only makes sense as an implementation detail. Objective-C's answer to this is the class extension: a special, unnamed variant of the category syntax you just learned, written directly inside the .m file, that lets a class declare additional API that only that file's own implementation can see or call.

The Anonymous Category Syntax: @interface ClassName ()

A class extension is written exactly like a category — @interface ClassName (SomeName) ... @end — except the parentheses are left empty: @interface Person () ... @end. That empty pair of parentheses is the entire syntactic difference between an ordinary named category and a class extension, and it is not a cosmetic detail; the compiler treats an empty-parens category specially, as a genuine extension of the class itself rather than as an add-on category.

Exactly as the spec example shows, a class extension for Person declaring a private property looks like this:

This block is placed near the top of Person.m, above the @implementation Person block, and by convention below the #import lines. It is never placed in Person.h. Anything declared inside it — here, the internalToken property — becomes a genuine part of the Person class, but only within the compilation unit (the .m file, and anything else that happens to #import it directly, which normally nothing does) that can see this extension block.

• Class extension syntax: @interface ClassName () ... @end — a category with an empty name. • Written at the top of the .m file, never in the public .h header. • Anything declared inside it becomes a real part of the class itself, not a bolted-on category.

Why Class Extensions Are the Standard Place for Private API

Every class you write tends to have two audiences: the callers who use it from outside (who only need Person's public API — -initWithName:age:, -description, and so on) and the class's own implementation, which often needs extra bookkeeping that callers should never see or touch directly. Objective-C headers are just text files that get copied verbatim into every file that imports them, so anything placed in the public @interface in Person.h is visible — and callable — from every single file that imports Person.h, with no real enforcement of privacy at the language level.

A class extension solves this cleanly: because it lives only in Person.m, a property like internalToken or a helper method like -refreshCache is completely invisible to any other file in the project. Code outside Person.m cannot reference person.internalToken at all — the compiler will report it as an unknown property, because as far as any other file is concerned, Person simply does not have that property. This is the idiomatic, everyday way experienced Objective-C developers keep a class's internal implementation details out of its public contract, and it should be your default home for any property, ivar, or method that exists purely to support the class's own internals rather than to be called by other code.

• Public API → declared in the .h file's main @interface block, visible to every importer. • Private/internal API → declared in a class extension in the .m file, invisible outside it. • Default rule: if a caller outside the class has no business using it, put it in the class extension, not the header.

The readonly-in-public, readwrite-in-private Pattern

One of the single most common uses of a class extension is redeclaring a property that is public as readonly — so outside code can read it but never write to it — as readwrite inside the class extension, so the class's own implementation can freely assign to it. In Person.h, you might expose @property (nonatomic, strong, readonly) NSString *name;, meaning any other file can write NSString *n = person.name; but cannot write person.name = @"New Name"; — the compiler simply rejects that assignment because, as far as that file knows, name has no setter.

Inside Person.m's class extension, you then redeclare the very same property, but as readwrite: @interface Person () @property (nonatomic, strong, readwrite) NSString *name; @end. This does not create a second, conflicting property — the compiler recognizes it as the same name property being given a wider set of permissions, and this redeclaration is only visible inside Person.m. The practical effect is exactly what you want from a well-designed public API: outside callers get a value they can read but never mutate directly, while Person's own methods (say, a -renameTo: method that validates the new name before applying it) are free to assign to self.name because, from inside Person.m, the property is readwrite.

This pattern is worth internalizing as a habit, not just a curiosity: whenever you're designing a class and find yourself wanting outside code to see a value without being able to freely overwrite it — bypassing whatever validation or side effects your setter method would otherwise run — a readonly public property paired with a readwrite class-extension redeclaration is very often the exact tool for the job.

• Public header: @property (nonatomic, strong, readonly) NSString *name; — callers can read, not write. • Private class extension: redeclare the same property as readwrite so the class's own code can assign it. • This is not two properties — it's one property, with a narrower public contract and a wider private one.

Where Class Extensions Live and Why That Matters

A class extension's placement is not arbitrary — it is placed in the .m file specifically because the .m file is the one compilation unit that is never #import-ed by other source files. Headers (.h files) exist to be shared; implementation files (.m files) exist to be compiled once, on their own, into object code. Because nothing else imports Person.m, whatever a class extension declares inside it simply never reaches any other file's view of the Person class, which is exactly the privacy guarantee the previous sections described.

It's also worth being precise about what 'private' means here: this is compile-time, source-level privacy, not runtime security. Objective-C's dynamic runtime (covered in depth in the Runtime module) can still be used to inspect or even invoke a class's private properties and methods by name at runtime, bypassing the compiler entirely, if someone deliberately goes looking. A class extension is a tool for keeping your own codebase's public contracts clean and your API surface intentional — it communicates to every other engineer on the project (including future-you) 'this is implementation detail, don't touch it from outside' — but it is not a security boundary against determined, deliberate misuse.

• Class extensions live in .m files because .m files are never imported by other source files. • This gives compile-time, source-level privacy — not runtime security against deliberate, reflection-style access. • Treat it as a contract with other engineers on the codebase, not a security wall.

Class Extension vs. Named Category: The Real Differences

The previous lesson introduced named categories — @interface Person (Formatting) ... @end — as a way to add methods to an existing class, even a class you don't own, often split across multiple files. A class extension looks almost identical, but the two are meaningfully different tools, not two spellings of the same thing.

First, and most importantly: a class extension can declare new properties and new instance variables, and a named category cannot. If you write @interface Person (Formatting) @property (nonatomic, strong) NSString *cache; @end, the compiler will accept the property declaration syntactically, but it will not synthesize backing storage (an ivar) for it, and self.cache = ... will fail at runtime with 'unrecognized selector' unless you manually wire up storage some other way. A class extension has no such limitation — internalToken in the spec's example gets a real, compiler-synthesized ivar exactly as if it had been declared in the main @interface, because the class extension is treated as a genuine part of the class's primary declaration, not a bolt-on.

Second, a class extension's methods must be implemented inside the class's own single @implementation Person @end block — there is no separate @implementation Person () @end. A named category, by contrast, gets and requires its own separate implementation block, @implementation Person (Formatting) @end, which is exactly what lets categories live in entirely separate files from the main class. Third, a class extension is resolved entirely at compile time, as part of building the one true Person class — the compiler effectively merges it into the main @interface before generating code — whereas a named category is a genuinely separate, addable unit of behavior that the Objective-C runtime attaches to the class, which is also why categories, unlike class extensions, cannot add ivars: by the time a category is being processed, the class's memory layout has already been fixed.

• Class extension: can declare new properties/ivars with real backing storage; category: cannot add ivars. • Class extension: implemented in the single shared @implementation ClassName @end; category: needs its own @implementation ClassName (Name) @end. • Class extension: resolved at compile time as part of the class itself; category: attached separately, and typically usable from any file, public or private.

Class Extension Declaration

Empty parentheses — no name inside them — is what makes this a class extension rather than a named category. Placed at the top of the .m file, above @implementation, never in the .h file. Can declare properties, ivars, and method prototypes, all invisible outside this .m file.

@interface ClassName ()

@property (nonatomic, strong) TypeName *privatePropertyName;

- (void)privateHelperMethod;

@end

Code Examples

This is the canonical shape of a class extension: written at the top of Person.m, it adds a private internalToken property to Person that is fully real (backed by a compiler-synthesized ivar) but completely invisible to any file that only imports Person.h.

@interface Person () Outside files can only read person.name; a direct assignment from outside Person.m fails to compile. Inside Person.m, the class extension's readwrite redeclaration lets Person's own methods assign to self.name, but only after running whatever validation the class wants — here, rejecting an empty newName.

@property (nonatomic, strong, readwrite) NSString *name;

A class extension is not limited to properties — it can also prototype private instance methods, here -regenerateInternalToken, which is implemented in the same @implementation Person block as every public method and is unreachable from any file outside Person.m.

• (void)regenerateInternalToken;

@interface Person ()

@property (nonatomic, strong) NSString *internalToken;

@end
// Person.h — public
@interface Person : NSObject
@property (nonatomic, strong, readonly) NSString *name;
@end

// Person.m — private
#import "Person.h"

@interface Person ()
@property (nonatomic, strong, readwrite) NSString *name;
@end

@implementation Person

- (void)renameTo:(NSString *)newName {
    if (newName.length > 0) {
        self.name = newName;   // allowed: readwrite inside Person.m
    }
}

@end
@interface Person ()

@property (nonatomic, strong) NSString *internalToken;

- (void)regenerateInternalToken;

@end

@implementation Person

- (instancetype)init {
    self = [super init];
    if (self) {
        [self regenerateInternalToken];
    }
    return self;
}

- (void)regenerateInternalToken {
    self.internalToken = [[NSUUID UUID] UUIDString];
}

@end

Important Rules

Class extension syntax is @interface ClassName () ... @end — empty parentheses, not a named category.

Place class extensions at the top of the .m file, never in the public .h header.

A class extension can declare new properties and ivars with real backing storage; a named category cannot.

A class extension's methods are implemented inside the class's one shared @implementation block, not a separate one.

readonly-in-public / readwrite-in-private via a class extension is the standard way to expose a safe-to-read, protected-from-outside-writes property.

Why Production Objective-C Code Is Full of Class Extensions

Open almost any well-maintained Objective-C class in a real codebase and you'll find a class extension at the top of its .m file, often longer than the public @interface in the .h file. A network manager class might expose only -fetchUserWithID:completion: publicly, while privately tracking an NSURLSession, a dictionary of in-flight request tasks, and a retry counter — all declared in a class extension, all invisible to every other file in the app. This keeps public headers small and focused on 'what this class does for you' while letting the implementation grow as complex as it needs to internally, which is exactly the separation of concerns that makes large Objective-C codebases maintainable over years of changes by many engineers.

Declaring @property (nonatomic, strong) NSString *internalToken; inside a named category, e.g. @interface Person (Private) ... @end, expecting it to work like a class extension.

Use the empty-parens form @interface Person () ... @end when you need to add a property, since categories cannot add ivars/backing storage.

A category with a name is still a category, even if you intend it as 'the private one' — it inherits every category limitation, including the inability to synthesize instance variables, so self.internalToken would crash at runtime with 'unrecognized selector'.

Putting the class extension's @interface Person () ... @end block inside Person.h so 'it's easier to find'.

Keep class extensions in Person.m, above @implementation, so the declarations stay invisible to every file that only imports Person.h.

Anything written into the .h file is copied into every importer's compiled view of the class, which defeats the entire purpose of using a class extension for private API in the first place.

Writing @implementation Person () @end to implement a class extension's methods, mirroring how a named category needs its own @implementation Person (Name) @end.

Implement class-extension methods inside the single, ordinary @implementation Person @end block used for every other method on the class.

Class extensions are merged into the class's primary declaration at compile time, so there is no separate implementation block for them — @implementation Person () @end is not valid syntax.

What makes @interface Person () ... @end a class extension rather than a named category? A. It is written in the .m file instead of the .h file B. The parentheses are empty — there is no category name inside them C. It uses @property instead of method declarations D. It is placed above @implementation instead of below it

B. The parentheses are empty — there is no category name inside them — The defining syntactic feature of a class extension is the empty parentheses. Location in the .m file is a strong convention that follows from what a class extension is for, but the empty-parens form is what the compiler actually treats specially.

Which of these can a class extension do that a named category cannot? A. Declare methods B. Be written in an .m file C. Declare a new property with real, compiler-synthesized backing storage (an ivar) D. Use @property syntax at all

C. Declare a new property with real, compiler-synthesized backing storage (an ivar) — Both can declare methods and use @property syntax, but only a class extension gets real ivar-backed storage for a new property; a category's @property declaration compiles but has no working storage behind it.

Why is a class extension's private API invisible to files that only import the class's .h header? A. Objective-C encrypts class extension declarations B. The class extension is declared in the .m file, which other source files never #import C. Private properties are removed by the compiler before linking D. Xcode hides class extensions from autocomplete only, but they are still callable

B. The class extension is declared in the .m file, which other source files never #import — Headers are meant to be shared via #import; .m files are compiled standalone and are not imported elsewhere in normal project structure, so anything declared only in the .m file's class extension never becomes visible to other files.

In the readonly/readwrite pattern, where should a property first be declared as readonly, and where should it be redeclared as readwrite? A. readonly in the .m file's class extension; readwrite in the public .h file B. readonly in the public .h file; readwrite in the .m file's class extension C. Both readonly and readwrite must be declared in the same block D. readonly and readwrite cannot both apply to the same property

B. readonly in the public .h file; readwrite in the .m file's class extension — The public .h file declares the property readonly so outside callers can read but not write it; the class extension in the .m file redeclares the same property readwrite so the class's own implementation can assign to it freely.

Where must a class extension's declared methods be implemented? A. In a separate @implementation Person () @end block B. In the class's own single @implementation Person @end block, alongside its public methods C. In a separate .m file dedicated to private methods D. They do not need to be implemented if only called internally

B. In the class's own single @implementation Person @end block, alongside its public methods — Unlike a named category, a class extension has no separate @implementation block — its declarations are merged into the class's primary declaration at compile time, so its methods belong in the ordinary shared @implementation block.

Add a private BOOL property named hasLoggedIn to Person using a class extension, and write a private method -recordLogin that sets it to YES. Make sure neither is visible from any file that only imports Person.h.

// Person.m @interface Person () @property (nonatomic, assign) BOOL hasLoggedIn; - (void)recordLogin; @end @implementation Person - (void)recordLogin { self.hasLoggedIn = YES; } @end

Start from @interface Person () ... @end at the top of Person.m, declare both the property and the method prototype there, and implement -recordLogin inside the existing @implementation Person block.