I started learning Objective-C because I wanted to program iPhone/iPad applications. I heard that the language had garbage collection, but I was disappointed when I read it only worked for Mac OS X. Coming from a .Net background, memory management seemed like advanced black magic to me. As it turns out though, it’s really not that complicated –if you follow certain rules.
Memory management in Objective-C is based on “object ownership”. An object can have one or more “owners”. The number of ”owners” is stored in each object using a mechanism called reference counting. You can think of this number as a counter of the number of “owners” an object has. Reference counting in Objective-C works like this:
· When you “create” an object its reference count is 1.
· When you “take ownership” of an object, you increase its reference count by 1.
· When you “release” an object, you decrease its reference count by 1.
· When you “autorelease” an object, you flag it to be released later by an auto release pool. When the object is released, its reference count it’s decreased by 1.
· When an object reference count is 0, the object is “deallocated” or destroyed. The objects “dealloc” method gets called automatically, and its memory footprint is 0.
· Once an object is “deallocated”, it cannot be used anymore.
So, how can you “create” or “claim ownership” of an object?
· You can create or “own an object”, by using a method whose name begins with “alloc” or “new”.
· You can “claim ownership” of an object when you call “retain”, or call a method that starts with “copy” or “mutableCopy”.
To clarify how you can “create” an object, I’ll be using the class I defined in my previous post.
Camera * myFirstCamera = [[Camera alloc] init];
I’m calling “alloc” therefore I “own” the object. Also, the myFirstCamera object reference count is 1.
I can now use the object…
[myFirstCamera focus];
[myFirstCamera takePicture];
When I’m done using the camera instance, I relinquish the object by calling release.
[myFirstCamera release];
The camera object reference count decreases by one, becomes equal to 0, and the camera object is destroyed.
If you want to use an object in a different scope from which it’s declared, you need to prevent it from being destroyed. You do this by “taking ownership” of the object. Storing an object inside a class is one example of when you would want to do this. To explain this, in the Camera.h interface file, I’m going add a manufacturer member variable.
NSString *manufacturer;
Next, I’m going to add accessor methods.
-(void) setManufacturer:(NSString *)manufacturerArg;
-(NSString*) manufacturer;
The syntax “:(NSString *) manufacturerArg” for sending an argument to a function is strange at first glance, but you'll get used to it the more you code.
Now to “claim ownership” of an object, we use a setter method.
-(void) setManufacturer:(NSString *)manufacturerArg{
if(manufacturerArg != manufacturer){
[manufacturer release];
manufacturer = [manufacturerArg copy];
}
}
The manufacturerArg is an object which we don’t own. Because we don’t own it, it might get deallocated at some point. This is one example, when you would want to “claim ownership” of an object. To accomplish this purpose, you can call “retain” or “copy”. Calling “retain” will copy the reference of the string object, and copy will return a new object that has the same content as the argument. Both “retain” and “copy” increase the reference count by one. Although when using strings, it’s preferable to use “copy”, because we don’t want external changes to affect the state of our variable. Before I call copy, I release the previous reference stored by calling “release”.
The implementation of the getter method in the Camera.m file is:
-(NSString*) manufacturer{
return [[manufacturer copy] autorelease];
}
I return a copy of the member variable, so external changes won’t affect the manufacturer member variable in my class. When I called copy, I increased the reference count by one. For this reason, I must balance the reference count by calling “autorelease”. In addition to this, when you call “autorelease”, the object is added to the current topmost autorelease pool. When the objects go out of context the autorelease pool calls the drain method, which decreases the reference count of all the objects by one. When developing iOS applications Xcode creates autorelease pools, for every event dispatch, you as a programmer don’t need to worry about it. You just need to know that one of the autorelease pools, will decrement the reference count of your object by one, at some point in the future. Of course, this auto release pools can also be created manually.
A corollary from the previous code is an important memory management rule.
You only call “release” or “autorelease” on objects you own.
Now since I added the manufacturer member variable, there is a reference to an NSString object now. In the setter methods, I’m retaining the object to prolong its life. However, the camera class as it stands now would leak memory. Why? Because, I’m not releasing the manufacturer object. Once the camera object is destroyed, there would be no way to reference the manufacturer variable. As a result, I’m creating a memory leak. To fix this, I need to call the release method inside the destructor.
-(void) dealloc{//destructor of the camera object.
[manufacturer release]; //fix the leak.
[super dealloc]; //the only time you should ever call the "dealloc" method.
}
By calling “release”, I balance the reference count in the camera object, and fix the leak. The [super dealloc] calls the NSObject dealloc function to release it’s memory. The super keyword refers to the base class(NSObject) of the camera object. This is the only context from which the “dealloc” should ever be called. In a way, this leads us to another very important memory management rule:
You should never call another objects dealloc method directly.
Okay, so now that we know about memory management let’s use our method.
NSString *manufacturer = @"Canon";
NSString *autoreleasedManufacturer = [NSString stringWithFormat:@"%@", manufacturer];
[myFirstCamera setManufacturer:autoreleasedManufacturer];
The stringWithFormat is a constructor method that returns an “autoreleased” string. The method doesn’t start with “alloc”, “new”, “copy” or “mutableCopy”, as a result you don’t own the autoreleasedManufacturer object, and you shouldn’t call release. If you implement your own methods, you should also follow the same convention.
Now let’s look at a situation when you would release a string.
NSString *mustReleaseManufacturer = [[NSString alloc] initWithFormat:@"%@", manufacturer];
[myFirstCamera setManufacturer: mustReleaseManufacturer];
[mustReleaseManufacturer release];
The mustReleaseManufacturer string is created using alloc, so I must release the object after using it.
To conclude, let’s mention the most important rules:
· You should only release or autorelease objects you own.
· You must override dealloc method to release the objects you own.
· Never call another objects dealloc method directly.