Objective-C
ivar
programming concepts
software development
memory management

Why would you use an ivar?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Objective-C, an instance variable (ivar) is a variable stored directly in an object's memory layout. While properties (with @property and @synthesize) are now the standard for most use cases, ivars still have valid uses: they provide direct memory access without getter/setter overhead, they keep implementation details truly private (not visible in the header), and they are necessary in certain performance-critical or low-level scenarios. Use ivars when you need private storage without KVO, no accessor overhead, or compatibility with legacy code.

Ivars vs Properties

Property (Standard Approach)

objectivec
1// MyClass.h
2@interface MyClass : NSObject
3@property (nonatomic, strong) NSString *name;
4@property (nonatomic, assign) NSInteger count;
5@end
6
7// MyClass.m
8@implementation MyClass
9// @synthesize generates _name and _count ivars + getter/setter methods
10@end

Properties create getter and setter methods. Accessing self.name calls -name (getter) or -setName: (setter), which may involve retain/release, KVO notifications, and thread locking (atomic).

Ivar (Direct Access)

objectivec
1// MyClass.h
2@interface MyClass : NSObject {
3    @private
4    NSString *_internalState;  // Declared in header (visible but private)
5}
6@end
7
8// MyClass.m — ivars in implementation block (truly private)
9@implementation MyClass {
10    NSInteger _cachedValue;
11    BOOL _initialized;
12}
13
14- (void)setup {
15    _cachedValue = 42;       // Direct memory access — no method call
16    _initialized = YES;
17}
18@end

Ivars declared in the @implementation block are invisible to subclasses and external code — they are truly private.

When to Use Ivars

1. Performance-Critical Code

objectivec
1@implementation ParticleSystem {
2    float *_positions;   // Raw C array for performance
3    NSInteger _count;
4}
5
6- (void)updatePositions {
7    // Tight loop — direct ivar access avoids method call overhead
8    for (NSInteger i = 0; i < _count; i++) {
9        _positions[i * 2] += _velocities[i * 2];      // x
10        _positions[i * 2 + 1] += _velocities[i * 2 + 1]; // y
11    }
12    // Using self.count in a tight loop would call the getter thousands of times
13}
14@end

In tight loops processing thousands of items per frame, the overhead of property getter/setter calls (even with nonatomic) can be measurable.

2. Truly Private State

objectivec
1// MyClass.h — clean public interface
2@interface MyClass : NSObject
3- (void)performAction;
4@end
5
6// MyClass.m — implementation details hidden
7@implementation MyClass {
8    NSMutableArray *_pendingOperations;  // Not in header at all
9    dispatch_queue_t _workQueue;
10    BOOL _isProcessing;
11}
12
13- (void)performAction {
14    if (_isProcessing) return;
15    _isProcessing = YES;
16    // ...
17}
18@end

Ivars in the @implementation block do not appear in the header file. They are invisible to consumers of the class, keeping the public API clean.

3. Avoiding KVO Side Effects

objectivec
1@implementation ViewModel {
2    NSString *_internalState;  // Not KVO-observable
3}
4
5// Property would trigger KVO notifications
6// @property NSString *internalState;
7
8- (void)updateState:(NSString *)newState {
9    _internalState = newState;  // No KVO notification, no setter overhead
10    // Manually notify only when needed
11    [self willChangeValueForKey:@"displayState"];
12    // Update derived properties...
13    [self didChangeValueForKey:@"displayState"];
14}
15@end

Properties are automatically KVO-compliant. If you modify a property frequently during internal computation and do not want observers triggered on every change, use an ivar.

4. C Types and Structs

objectivec
1@implementation MapRenderer {
2    CGRect _visibleRect;
3    CGAffineTransform _currentTransform;
4    struct {
5        unsigned int needsRedraw: 1;
6        unsigned int isAnimating: 1;
7        unsigned int didLayout: 1;
8    } _flags;  // Bitfield struct — no property equivalent
9}
10
11- (void)setNeedsRedraw {
12    _flags.needsRedraw = 1;
13}
14@end

C structs, bitfields, and unions cannot be meaningfully wrapped in properties. Ivars are the natural choice for these types.

5. Delegate Flag Caching

objectivec
1// Common pattern in Apple's frameworks
2@implementation MyTableView {
3    struct {
4        unsigned int delegateHeightForRow: 1;
5        unsigned int delegateWillDisplayCell: 1;
6        unsigned int delegateDidSelectRow: 1;
7    } _delegateFlags;
8}
9
10- (void)setDelegate:(id<MyTableViewDelegate>)delegate {
11    _delegate = delegate;
12    // Cache which methods the delegate implements (once, not per call)
13    _delegateFlags.delegateHeightForRow = [delegate respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)];
14    _delegateFlags.delegateWillDisplayCell = [delegate respondsToSelector:@selector(tableView:willDisplayCell:forRowAtIndexPath:)];
15}
16
17- (CGFloat)heightForRow:(NSIndexPath *)indexPath {
18    if (_delegateFlags.delegateHeightForRow) {
19        return [_delegate tableView:self heightForRowAtIndexPath:indexPath];
20    }
21    return 44.0;  // Default height
22}
23@end

This is a well-known performance pattern from Apple's UIKit source. The bitfield ivar caches respondsToSelector: results to avoid repeated message sending.

Property vs Ivar Summary

FeaturePropertyIvar
Accessor methodsYes (getter/setter)No (direct access)
KVO compatibleYes (automatic)No
Memory managementManaged by setter (strong/weak/copy)Manual in MRC, automatic in ARC
Visible in headerYes (public API)Optional (can be in .m)
Subclass accessYesOnly if in header/@protected
Thread safetyOptional (atomic)None built-in
PerformanceSlight overheadDirect memory access

Common Pitfalls

  • Forgetting memory management (MRC): Under Manual Reference Counting, assigning to an ivar does not retain the object. _name = newName leaks the old value and does not retain the new one. Under ARC, this is handled automatically. Always use ARC for new code.
  • Accessing ivars from outside the class: Ivars declared in the header with @public are accessible as object->_ivar, which breaks encapsulation. Always declare ivars as @private or put them in the @implementation block.
  • Using self->_ivar in blocks: Under ARC, _ivar inside a block implicitly captures self, creating a potential retain cycle. Use __weak typeof(self) weakSelf = self and access weakSelf->_ivar or convert to a property accessed through weakSelf.property.
  • Subclass cannot access implementation ivars: Ivars declared in @implementation MyClass { ... } are invisible to subclasses. If a subclass needs access, move the ivar to the header's @protected section or provide a property.
  • Using ivars when properties are clearer: Overusing ivars reduces code clarity. Properties document the intent (strong vs weak vs copy, atomic vs nonatomic). Only use ivars when you have a specific reason — properties are the default best practice.

Summary

  • Use ivars for truly private state, performance-critical paths, C structs/bitfields, and KVO avoidance
  • Ivars declared in @implementation are invisible outside the class — cleaner headers
  • Properties are preferred for most use cases — they provide memory management, KVO, and thread safety
  • Direct ivar access (_name) avoids getter/setter overhead, useful in tight loops
  • Under ARC, ivar memory management is automatic — no manual retain/release needed
  • Apple's own frameworks use ivars extensively for delegate flag caching and internal state

Course illustration
Course illustration

All Rights Reserved.