Objective-C applications receive a memory warning from the system when memory usage exceeds approximately 20 MB. Upon receiving this warning, the application must release unnecessary memory by deallocating unused objects and variables to prevent crashes.
Memory management in Objective-C specifically handles objects that inherit from NSObject, not basic data types. This distinction arises from their storage differences: local variables reside in the stack, while objects are allocated in the heap. When a code block ends, its local variables are automatically reclaimed, including pointers to objects. However, the objects themselves remain in heap memory without any references, leading to memory leaks.
Since objects created with new or similar methods reside in the heap, memory management primarily focuses on heap-allocated content.
Core Principles of Memory Management
1. Ownership and Reference Counting
- Any object can have one or more owners. The object persists as long as at least one owner exists.
- According to Cocoa's ownership policy, you own any object you create using methods with names beginning with "alloc", "new", or containing "copy". You can also assume ownership by calling
retainon an object.
2. Reference Counter
- Each Objective-C object has an integer reference counter indicating how many references point to it. Upon creation, this counter defaults to 1. When the counter reaches 0, the object is destroyed.
- Internally, every object reserves 4 bytes to store its reference count, accessible via
retainCount.
3. Purpose of Reference Counting
- The primary criterion for determining whether an object should be deallocated is if its reference counter is 0 (with the exception of
nil, wich has a count of 0 but occupies no memory).
4. Operations on Reference Counters
- Send messages to objects to modify their reference counters:
retain: Increments the counter by 1 and returns the object itself.release: Decrements the counter by 1 (does not immediately free the object).retainCount: Returns the current reference count (use%ldor%tufor logging).
5. Object Deallocation
- When an object's reference counter hits 0, it is deallocated, and its memory is reclaimed by the system.
- Upon deallocation, the system automatically sends a
deallocmessage to the object. Overridedeallocto release related resources, akin to an object's final message. Always call[super dealloc]at the end of the overridden method. Do not calldeallocdirectly. Once deallocated, the object's memory is invalid; accessing it leads to crashes (dangling pointer errors).
Notes:
- Objects with a non-zero reference count persist in memory until the program exits.
- Newly created objects start with a reference count of 1.
- Using
alloc,new, orcopyto create an object sets its reference count to 1.
Memory Management Approaches
Three primary methods exist:
- Manual Reference Counting (MRC): Used in projects targeting iOS versions before 4.1, requiring explicit calls to
retain,release, andautorelease. - Automatic Reference Counting (ARC): Introduced in iOS 4.1, automatically manages reference counts; Apple recommends this for modern development.
- Garbage Collection: Not supported on iOS.
Getting Started with Manual Memory Management
To use MRC in an Xcode project (which defaults to ARC), disable ARC:
- Navigate to Project → Build Settings → Basic → Search for "auto" → Set Objective-C Automatic Reference Counting to NO.
Key practices in MRC:
- Determine if an object needs deallocation by checking its reference count (>0 means keep, 0 means release).
- Override
deallocto clean up resources, ensuring[super dealloc]is called last. - Use
releaseto decrement andretainto increment the reference count. - Balance reference counts: the total number of
retainandnewcalls should equalreleasecalls.
Example:
Person *individual = [Person new];
NSLog(@"Retain count: %tu", [individual retainCount]);
[individual retain];
[individual release];
NSLog(@"Retain count: %tu", [individual retainCount]);
[individual release];
Guidelines for Overriding dealloc
- Always call
[super dealloc]at the end to ensure proper cleanup of parent class resources. - Release any objects owned by the current instance within
dealloc.
Example:
- (void)dealloc {
[_vehicle release];
[super dealloc];
}
Caution:
- Never invoke
deallocdirectly on an object. - After deallocation, set pointers to
nilto avoid dangling pointer errors.
Memory Management Rules
- An object should not be deallocated while it is still in use.
- Increase an object's reference count when you intend to use it; decrease it when done.
- Follow "who creates, who releases": If you create an object via
alloc,new, orcopy, you must callreleaseorautorelease. - Follow "who retains, who releases": Any call to
retainnecessitates a correspondingrelease. - Ensure symmetry: every increment in reference count should eventually be matched by a decrement.
Key Issues in Memory Management
1. Dangling Pointers (Zombie Objects) Occur when an object is deallocated, but pointers to it remain. Accessing such pointers can cause undefined behavior or crashes.
Example:
Person *endividual = [Person new];
[individual release]; // individual becomes a dangling pointer
// Enabling zombie detection in Xcode (Edit Scheme → Diagnostics → Enable Zombie Objects) helps identify errors.
// individual = nil; // Safely nullify the pointer to prevent misuse
Distinctions:
nil: Null pointer to an Objective-C object.Nil: Null pointer to an Objective-C class.NULL: Generic null pointer.NSNull: Singleton object representing null in collections (which don't allownil).
2. Memory Leaks Happen when objects are not deallocated due to unbalanced reference counts or incorrect pointer assignments.
Common scenarios:
- Mismatched
retainandreleasecalls. - Assigning
nilto an object pointer without proper release. - Unnecessary
retaincalls within methods.
Example of a leak:
Vehicle *car = [[Vehicle alloc] init];
[car retain];
[car retain];
car = nil; // Leak: original Vehicle object not released
[car release]; // No effect on nil
Managing Multiple Objects
For properties involving Objective-C objects, implement custom setter methods to manage ownership:
- (void)setVehicle:(Vehicle *)newVehicle {
if (_vehicle != newVehicle) {
[_vehicle release];
_vehicle = [newVehicle retain];
}
}
Retain Cycles
Occur when two objects retain each other, preventing deallocation. Break cycles by using assign instead of retain for one property or manually releasing references.
Example of a cycle:
// ClassA retains ClassB, and ClassB retains ClassA
// Solution: Use assign for one property or ensure manual release
Special Considerations for NSString
NSString objects often have high default reference counts due to internal optimizations. Avoid relying on retainCount for memory debugging with strings, as results can be unpredictable.
Example:
NSString *text = [[NSString alloc] initWithString:@"Sample"];
text = @"New"; // Potential leak of "Sample"
[text release]; // Releases constant "New", which has a high retain count
Autorelease Mechanism
Autorelease defers release calls by adding objects to an autorelease pool, which sends release to all contained objects upon drainage.
Creating Autorelease Pools:
- Pre-iOS 5:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; ... [pool release]; - iOS 5+: Use
@autoreleasepool { ... }blocks.
Usage:
- Call
autoreleaseon an object to place it in the current autorelease pool. - The pool automatically handles
releasewhen drained, either manually or at the end of a run loop.
Example of a factory method:
+ (instancetype)createPerson {
return [[[self alloc] init] autorelease];
}
When Autorelease Pools Are Drained:
- Manually via
releaseordrain. - Automatically at the end of a run loop iteration (e.g., after UI events, timers).
Best Practices:
- Use
autoreleasewithin an autoreleace pool block. - Employ autorelease for convenience methods that return newly created objects.
- Prefer
instancetypeoveridin factory methods for type safety.