Wednesday, November 3, 2010

An Introduction to Memory Management in Objective-C pt.2

Hello again!
In this follow-up I will explain the allocators and destructors available on Objective-C.


alloc


alloc allocates memory for an object of a given class, but DOES NOT initialize this object. Usually, alloc comes together with some initializer like initWithString or simply init. For example:


NSString* foo = [[NSString alloc] initWithString:@"foo"];



new


new is a convenience method equivalent to [[Class alloc] init]. This is not documented though, and was subject of a thread on cocoa-dev mailing list back in 2008. Bill Bumgarner, who is an Apple Software Engineer, explained it on the list:



Bill Bumgarner:
I just checked all the way back to 10.1. The implementation was +allocWithZone: NULL followed by -init until Leopard, when it moved to +alloc followed by -init.



copy


This is the keyword for the copy constructor. It allocates memory and initializes an object with the copy of the sender. Example:


NSString* foo = [[NSString alloc] initWithString:@"foo"];
NSString* fooCopy = [foo copy]; // fooCopy == @"foo"



A class must implement the NSCopying Protocol in order to use the copy constructor. This is accomplished by completing 2 steps:
1. Extend NSObject <NSCopying> instead of NSObject, for example.
2. Implement the copyWithZone method.


@interface MyClass : NSObject <NSCopying>
{
NSString* attribute_string;
}
@property(readwrite,copy) NSString* attribute_string;

- (id) copyWithZone:(NSZone*)zone;

@end

@implementation MyClass

@synthesize attribute_string;

- (id) copyWithZone:(NSZone*)zone {
MyClass* copy = [super allocWithZone:zone];

copy.attribute_string = [self attribute_string];

return copy;
}



Note: The implementation above uses @property and @synthesize just to handle the setters and getters. I am going to write a tutorial about properties later on, but for now just imagine that " copy.attribute_string = " is replaced by copy->setAttributeString(...), and setAttributeString is a public method that deals with the string copying and releasing.


retain


retain is used to get ownership over a reference you didn't allocate yourself. A common usage of retain is to hold references of objects inside collections.


Usually, core collections, like NSArray, NSDictionary and their mutable counterparts, release all objects once the container is released. This can create invalid references, if one needs to retain a reference to an object of the container for longer than the container itself. For example:


NSArray* students = [[NSArray alloc] initWithObjects:@"Alice",
@"Ben",
@"Chris",
@"David",
nil];
NSString* studentName = [students objectAtIndex:2]; // @"Chris"
[students release];
NSLog(@"Student no 2: %@", studentName);
// Error: studentName is an invalid pointer.



To workaround this, one should retain the studentName reference before releasing the container, like this:


NSString* studentName = [[students objectAtIndex:2] retain];
[students release];
NSLog(@"Student no 2: %@", studentName);
// prints "Student no 2: Chris" to console.

[studentName release];



release


release relinquishes ownership of an object. It decrements the reference count to that object and if it gets to 0, then the object gets dealloc'd.


It is important to notice that one should never send a dealloc message directly. Instead, objects should be released and the deallocation will happen automatically either by the OS or the memory management environment.


autorelease


autorelease is a kind of lazy-release. When an object receives the autorelease message, the object ownership is transferred to the last created autorelease pool, thus extending the lifetime of the object and simplifying memory management.


An autorelease pool is a space where all references to objects that received an autorelease message are stored. When the pool is drained (same as deallocated), these references receive a release message and then gets deallocated.


Therefore, there are 2 basic implications of using autorelease:
Either use release or autorelease, and never both.
Always make sure you have an autorelease pool created before you send an autorelease message, or Cocoa will log an error.


Autorelease pools can be nested and are stored in a stack. This means that one can't extend the lifetime of an object, using autorelease, more than the last created pool's lifetime. Also, this mean that if you are not sure an autorelease pool was created, you should always create an autorelease pool before using autorelease.


And finally, one should use autorelease when he needs to create an object inside a scope, that will be used outside this scope.


For example:


// arrayFactory.m
@implementation arrayFactory

+ (NSArray*) createArray {
NSArray* newArray = [[NSArray alloc]
initWithObjects:@"a",@"b",@"c",nil];
return [newArray autorelease];
}

@end

// main.m
int main(int argc, char *argv[])
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

NSArray* myArray = [arrayFactory createArray];

// usage of myArray

[pool drain]; // releases myArray and its content.
return 0;
}



This is all one needs to know to be confident when dealing with memory management in Objective-C.


I also recommend reading Apple's Developer Docs about Memory Management for some advanced tips regarding performance and memory footprint.


Until next time!