Instance Variables: Backing Storage, Naming, Visibility, and Access Style
Explain what instance variables actually are as per-object storage, understand the _propertyName synthesis convention, distinguish property-backed ivars from explicitly declared ones, apply @private/@protected/@public visibility correctly, and choose correctly between direct ivar access and property access inside a class's own methods.
Every example so far — the Person class's _name and _age in Class Anatomy, the synthesized _propertyName backing storage from the Properties lesson — has quietly depended on instance variables, the actual, per-object memory slots where an object's state physically lives. Properties are a convenient interface built on top of instance variables, not a replacement for them; understanding ivars directly — how they're named, who can see them, and when to reach past a property's accessor and touch one directly — rounds out the full picture of what an Objective-C object actually is underneath its @property declarations.
What an Instance Variable Actually Is
An instance variable (ivar) is a variable whose storage exists once per instance of a class — when you create two separate Person objects, alice and bob, each one gets its own independent copy of every ivar the Person class declares; alice's _name and bob's _name are two entirely different memory locations that happen to share a name in source code, exactly the same way two different Person instances have entirely separate identities even though both are built from the same class blueprint. This is different from a static variable inside a method (covered back in the C Constants and Variables lessons), which is shared across every call regardless of which instance is involved — an ivar is deliberately the opposite: state that belongs to one specific object.
Ivars can be declared two ways: implicitly, through property synthesis (a @property declaration silently creates a backing ivar for you, as covered in the previous lesson), or explicitly, by listing them directly inside braces in the class's @interface or @implementation: @interface Person : NSObject { NSString *_nickname; NSInteger _internalScore; } @end. Explicitly declared ivars have no accessor methods generated for them at all — no getter, no setter — unlike property-backed ivars; if you want _nickname readable or writable from outside Person's own methods, you have to either write accessor methods for it by hand or expose it through a genuine @property instead.
Regardless of how an ivar came to exist, its actual physical storage lives inside the memory block that alloc originally reserved for that specific instance — this is exactly what was happening back in the Class Methods lesson when +alloc allocated 'enough memory sized correctly for a Person instance': that memory is sized to hold every one of Person's ivars, synthesized and explicit alike, laid out one after another for that one object.
• An ivar's storage exists once per instance — each object has its own independent copy, unlike a static variable shared across all calls. • Implicit ivars: auto-created by @property synthesis (backing storage for a declared property). • Explicit ivars: declared directly in braces, e.g. @interface Person : NSObject { NSString *_nickname; } @end — no accessors generated automatically. • All of an instance's ivars, synthesized and explicit, live inside the single memory block +alloc reserved for that specific object.
The _ivar Naming Convention
When the compiler synthesizes a backing ivar for a property, its default name is the property's name with a leading underscore: a property declared @property (nonatomic, copy) NSString *name; gets a synthesized ivar named _name, exactly matching what Class Anatomy showed being assigned directly inside -initWithName:age: (_name = name;). This underscore prefix is a deliberate, load-bearing convention, not decoration — it visually distinguishes 'this is raw storage, accessed directly, bypassing the accessor' from self.name, which sends a message through the accessor method and all the logic (copying, memory management semantics) that method implements.
It's possible to override this default name (older Objective-C syntax allowed @synthesize name = _customName; to pick a different backing ivar name), but this is rarely done in modern code, since Xcode and the compiler handle synthesis automatically now and the _propertyName convention is universally recognized — deviating from it mostly just makes code harder for other Objective-C developers (and your future self) to read at a glance, since _name immediately signals 'the ivar for the name property' everywhere in the codebase.
The convention also matters because it prevents an easy naming collision: since the ivar is _name and the property (and its accessor methods) are name, you can freely use _name inside a method without it being confused with a local variable or parameter that might also be named, say, name — the underscore keeps the two namespaces visually and syntactically separate at every point they're used.
• A property named name gets a synthesized backing ivar named _name by default — a leading underscore. • The underscore visually flags 'raw storage access, bypassing the accessor' versus self.name, which calls the accessor method. • The default name can be overridden via older @synthesize syntax, but modern code almost universally keeps the automatic _propertyName convention. • The underscore also avoids collisions with local variables or parameters that share the property's base name.
Property-Backed Ivars vs. Explicitly Declared Ivars
A property-backed ivar (like _name, from @property ... NSString *name;) always has a matching pair of accessor methods available — even if you never call them directly, -name and -setName: exist and can be reached from outside the class through self.name or [person name]. An explicitly declared ivar (like _internalCache declared directly in braces, with no corresponding @property) has no such accessors by default — it is only reachable, from anywhere, by referencing _internalCache directly, and only from code that has visibility into it (covered next), which in practice usually means only from inside that class's own @implementation.
This makes explicitly declared ivars a natural fit for genuinely internal, implementation-only state that the class never intends to expose in any form — a cache, a flag tracking some internal bookkeeping detail, a helper object used only to compute something else — where going to the trouble of declaring a full @property (with its accessor generation, its ownership-attribute decisions) would be more machinery than the value's actual role warrants. If you later decide external code (or even just other methods needing a cleaner interface) should be able to read or write that value, promoting it to a real @property is the natural next step.
A particularly common and idiomatic place to put explicitly declared ivars is inside a private class extension — @interface Person () { NSString *_internalCache; } @end, placed at the top of the .m file — which keeps genuinely private, implementation-only ivars invisible to anyone importing Person.h, while still giving Person's own methods direct access to them. The full mechanics and additional capabilities of class extensions (including the readonly/readwrite property-redeclaration pattern from the previous lesson) are covered in their own dedicated module, Class Extensions, later in this course.
• Property-backed ivars always have a matching getter (and usually setter) accessible via self.propertyName or [obj propertyName]. • Explicitly declared ivars have no automatic accessors — reachable only by direct reference, from code with visibility into them. • Explicit ivars fit genuinely internal state (a cache, an internal flag) that doesn't warrant a full @property with accessors. • Common pattern: declare private ivars inside a class extension in the .m file — @interface Person () { NSString *_internalCache; } @end — invisible outside that file; full treatment in the Class Extensions module.
Visibility: @private, @protected, @public
Ivars declared inside a class's @interface (the header, typically visible to any file that imports it) can be marked with one of three visibility keywords, which control which code is allowed to reference that ivar directly by name. @private restricts the ivar to being directly accessible only from within that exact class's own @implementation — not even a subclass can reach it by direct ivar name. @protected widens that to the declaring class and any of its subclasses. @public allows any code anywhere that has a pointer to the object to reach the ivar directly, with no restriction at all — a choice that's almost always avoided in real Objective-C code, since it defeats the encapsulation that properties and accessor methods are specifically designed to provide.
If you declare an ivar in the braces of an @interface in a .h file without specifying any of these three keywords, the default visibility is @protected — accessible to the declaring class and its subclasses, but not to arbitrary outside code. This default is a deliberate middle ground: it lets a class hierarchy share internal state naturally (a subclass extending Person might legitimately want direct access to _name) while still keeping that state out of reach for unrelated code that merely holds a Person pointer.
Ivars declared inside braces in an @implementation block instead (rather than the @interface) — or inside a class extension in the .m file, as shown above — are effectively private to that file: because nothing outside that .m file (in particular, nothing that only imports the public .h) can even see the declaration exists, visibility keywords are largely moot there; the ivar is already as private as @private would make it, simply by virtue of living outside the publicly visible header. This is, in practice, the most common and most recommended place to put genuinely internal ivars in modern Objective-C, precisely because it achieves strong privacy without needing to reason about the three visibility keywords at all.
• @private: directly accessible only within the declaring class's own implementation — not even subclasses. • @protected: accessible within the declaring class and its subclasses — the default visibility for ivars declared (unmarked) in an @interface. • @public: accessible from any code with a pointer to the object — almost always avoided since it defeats encapsulation. • Ivars declared in an @implementation or a .m-file class extension are invisible outside that file entirely — the most common way to keep ivars genuinely private in modern code.
Direct Ivar Access vs. Property Access: A Style Rule
Given that _name (the raw ivar) and self.name (the property accessor) both ultimately refer to the same underlying storage, a real style question arises: which should your own code use, and when? The widely followed convention in Objective-C is this: inside a class's own -init... methods and its -dealloc method, prefer direct ivar access (_name = name;); everywhere else — ordinary instance methods doing their normal work — prefer property access (self.name = ...).
The reasoning for preferring direct ivar access specifically during initialization and deallocation comes down to correctness during a half-built or half-torn-down object. A custom setter (if you wrote one, or a property that participates in KVO — Key-Value Observing, a notification mechanism covered later in this course) can trigger side effects: notifying observers, running validation logic, or other bookkeeping that assumes the object is in a normal, fully-formed state. During -init, before the object is fully constructed, or during -dealloc, while it's in the middle of being torn down, triggering those side effects can be actively wrong — code reacting to a change notification might try to use other parts of the object that aren't set up yet (in -init) or have already been cleaned up (in -dealloc). Direct ivar access sidesteps the accessor entirely, so none of that logic runs, which is exactly what's wanted at those two specific, unusually fragile moments in an object's lifecycle.
Everywhere else — in a regular instance method going about its normal business, well after initialization has finished and long before deallocation begins — property access (self.name) is generally the better default, because it consistently goes through whatever semantics the property was declared with: a copy property's setter making its defensive copy, a property's KVO notifications firing correctly for observers who legitimately want to know about the change, and so on. Bypassing the accessor with direct ivar access in ordinary methods means silently skipping all of that defined behavior, which can reintroduce exactly the bugs those attributes exist to prevent — for instance, assigning _name = mutableCallerString; directly skips the copy property's defensive-copy behavior, exposing the class to the same mutable-subclass hazard the Properties lesson described.
• Convention: use direct ivar access (_name = ...) inside -init and -dealloc; use property access (self.name) elsewhere in ordinary instance methods. • Reason for init/dealloc: bypasses accessor side effects (like KVO notifications) during a half-constructed or half-destroyed object, when those side effects can be actively harmful. • Reason for elsewhere: property access consistently applies the property's real semantics (copy, memory management, notifications) every time. • Bypassing a copy property's setter with direct ivar assignment in ordinary code reintroduces the mutable-subclass hazard the copy attribute exists to prevent.
Code Examples
_name comes from the public @property and has real accessors (name, setName:) reachable from outside Person. _internalAccessCount is declared explicitly inside a class extension in the .m file -- it has no accessors at all, is invisible to any code that only imports Person.h, and can only be touched by referencing it directly, as shown inside -description.
initWithName: and dealloc both use direct ivar access (_name), the recommended style during construction and teardown, since the object isn't in a normal, fully-usable state yet (or anymore) and accessor side effects could be unsafe. renameTo:, an ordinary instance method running on a fully-formed object, uses self.name instead, so it goes through the real setter and gets the property's declared copy semantics consistently.
@interface Person : NSObject { NSString *_nickname; } @end // elsewhere, outside Person entirely: NSLog(@"%@", person->_nickname);
// If external access is genuinely needed, expose it as a real @property instead: @property (nonatomic, copy) NSString *nickname; // then: NSLog(@"%@", person.nickname);
An ivar declared with default (or @protected) visibility in an @interface is not meant to be reached directly from unrelated outside code, and doing so (even though the -> operator can technically compile in some cases) bypasses any accessor logic entirely and breaks encapsulation. If outside code needs the value, expose it properly through a @property.
• (instancetype)initWithName:(NSString *)name { self = [super init]; if (self) { self.name = name; // property access during init } return self; } - (instancetype)initWithName:(NSString *)name { self = [super init]; if (self) { _name = [name copy]; // direct ivar access during init } return self; }
Using self.name inside -init sends a full message through the setter, which can trigger side effects (like KVO notifications) on an object that isn't fully constructed yet -- a subclass overriding setName: might assume other ivars are already set up, and they may not be. Direct ivar access avoids this during initialization.
• (void)updateName:(NSString *)newName { _name = newName; // direct ivar access in an ordinary method, bypassing copy semantics } - (void)updateName:(NSString *)newName { self.name = newName; // goes through the setter's declared copy behavior }
If name is declared copy, assigning directly to _name in an ordinary instance method skips the defensive copy the setter would have performed, reintroducing the mutable-subclass hazard the copy attribute exists to prevent. Outside of init/dealloc, prefer property access so the property's real semantics are applied consistently.
What does it mean for an ivar's storage to exist 'once per instance'? A. Every instance of the class shares one single copy of that ivar B. Each separate object created from the class gets its own independent copy of that ivar's storage C. The ivar is deleted after the first method call D. It means the ivar can only be set once, ever
B. Each separate object created from the class gets its own independent copy of that ivar's storage — Unlike a static variable (shared across all calls/instances), each instance -- alice, bob, or any other Person -- has its own separate memory for each ivar the class declares, which is why two Person objects can have different names simultaneously.
What is the default name of the ivar synthesized for a property declared @property (nonatomic, copy) NSString *name;? A. name B. _name C. nameIvar D. mName
B. _name — Synthesis names the backing ivar with a leading underscore by default -- _name -- visually distinguishing direct storage access from self.name, which goes through the generated accessor method.
What is the key difference between a property-backed ivar and an explicitly declared ivar (declared directly in braces with no matching @property)? A. There is no real difference; they behave identically B. A property-backed ivar has real getter/setter accessor methods automatically available; an explicitly declared ivar has no accessors and is only reachable by direct reference C. Explicitly declared ivars are always public D. Property-backed ivars cannot be accessed directly at all, ever
B. A property-backed ivar has real getter/setter accessor methods automatically available; an explicitly declared ivar has no accessors and is only reachable by direct reference — A @property automatically generates accessor methods alongside its backing ivar; an ivar declared directly in braces with no matching @property has no such accessors and can only be reached by referencing it directly, typically only from within the class's own implementation.
What is the default visibility for an ivar declared, without an explicit keyword, inside the braces of a class's public @interface? A. @private B. @protected C. @public D. There is no default; it's a compiler error
B. @protected — Unmarked ivars declared in an @interface default to @protected -- accessible to the declaring class and its subclasses, but not to arbitrary unrelated code that merely holds a pointer to the object.
Why is direct ivar access (_name = ...) generally preferred over property access (self.name = ...) inside a class's own -init and -dealloc methods? A. Direct ivar access is always faster and that's the only reason B. Property access is not legal syntax inside init or dealloc C. Direct ivar access skips the accessor's side effects (like KVO notifications), which can be unsafe to trigger while the object is only half-constructed or half-destroyed D. There is no actual convention; both are always equally recommended everywhere
C. Direct ivar access skips the accessor's side effects (like KVO notifications), which can be unsafe to trigger while the object is only half-constructed or half-destroyed — During init (before the object is fully set up) and dealloc (while it's being torn down), triggering an accessor's side effects -- such as KVO notifications that other code might react to by touching not-yet-initialized or already-cleaned-up state -- can cause real bugs, so direct ivar access is the safer, conventional choice at those two points specifically.
// Person.h
@interface Person : NSObject
@property (nonatomic, copy) NSString *name; // synthesizes _name + accessors
@end
// Person.m
@interface Person () {
NSInteger _internalAccessCount; // explicitly declared, no accessors, .m-file private
}
@end
@implementation Person
- (NSString *)description {
_internalAccessCount++; // direct access -- no accessor exists for this ivar
return [NSString stringWithFormat:@"%@ (accessed %ld times)", self.name, (long)_internalAccessCount];
}
@end@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self) {
_name = [name copy]; // direct ivar access inside init -- skips any accessor side effects
}
return self;
}
- (void)dealloc {
NSLog(@"Deallocating person named %@", _name); // direct access again -- object is being torn down
}
- (void)renameTo:(NSString *)newName {
self.name = newName; // property access in an ordinary method -- goes through the real setter
}
@end