C# to Objective-C - Part 8 : Memory Management

Probably one of the things that worried me the most about moving from C# to Objective-C was having to manually manage memory for myself. For years I was spoiled by .NET's automatic Garbage Collector. Now, not only would i be responsible for managing memory directly, but it'd also be on a device that had much less memory than a PC. 

Have you had that same thought? Well, today I'm going to show you that managing memory yourself is actually pretty simple in objective-c, and as of XCode 4, its something you actually don't even have to do manually.  I'm going to go over the basics but if you want to learn more, Apple has done a really good job at providing some tips and tricks regarding memory management

In XCode 4, Apple introduced ARC, which allows the compiler to manage memory for you. ARC stands for Automatic Reference Counting, and to understand how it works, let me walk you through how memory management works without ARC.

Basically, any object you create in memory has a "Retain Count". So, when you create an object yourself, you automatically own that object, and its retain count has already been incremented for you.

MyObject* obj = [[MyObject alloc]init];

So in this scenario if we do nothing, when the method that we created this object in ends, the retain count will remain one and the object will go on living in memory even though we don't have a reference to it. So, unless I'm storing this object somewhere, where i want it to live, its on me to tell the framework that I no longer need this object. So for this scenario, lets assume i create the object and I no longer need it. This means we need to decrement its retain count:

[obj release]

Now, b/c i've decremented its retain count, at that point it checks to see if the count is zero and if it is the memory for that object is released. If i stored a reference to that object, and I tried to access it, I will get "Bad Memory" exception, and my application will crash. 

Lets review what happend: 

  • We created the object: obj retain count = 1;
  • We released the object: obj retain count = 0, since its zero the object's memory gets released. 

Pretty simple so far, but thats a very simple case as its local scope and i don't need it alive. Before we go any further, i should first mention the scenarios in which its you're responsibility to release an object. B/c if you over release an object, you'll wind up in another memory exception. 

You are responsible for releasing an object if: 

  1. You create an object with a method that begins with: "alloc", "new", "copy", "mutableCopy". 
  2. You call the retain method on an object. 

If i create the object its on me to release it and if didn't create the object but i called retain on it, its on me to release it. But what happens if I create a method that returns an object i created: 

-(MyObject*)createObjectWithVal:(int)val
{
   MyObject* obj = [[MyObject alloc]init];
   obj.val = val;
   return obj;
}

Hmm, according to the rules above, that means  since I created the obj, I must release it, otherwise it'll be a memory leak. However, if I release obj, then the consumer of this method will get an object returned that is invalid and when they access it, they'll get a memory exception. So what do we do?

Simple, use "autoRelease":

-(MyObject*)createObjectWithVal:(int)val
{
   MyObject* obj = [[MyObject alloc]init];
   obj.val = val;
   return [obj autoRelease];
}

This essentially defers decrementing the release count until we reached the end of the current release pool. So the user that invokes this method will get a valid object. But, b/c it will get released at the end of the scope, its on the consumer of the method at that point to retain it if they want to keep it alive:

-(void)viewDidLoad
{
   MyObject* obj = [self createObjectWithVal:5];
   _obj = obj;
}

In the code above, if i try accessing _obj in another method, i'll get a "Bad Memory" exception, b/c i didn't retain the object. So what i should do is: 

-(void)viewDidLoad
{
   MyObject* obj = [self createObjectWithVal:5];
   [obj retain]
   self->_obj = obj;
}

This means that i've taken ownership of it. And since i've taken ownership of it, that means that later on, its on me to release it. If i want _obj to live for as long as the object that owns it is alive, I simply need release it in dealloc:

-(void)dealloc
{
   [_obj release];
   [super dealloc];
}

That covers the basic use cases of "manual" memory management. Although I do want to show you one more somewhat advanced use case that could come in handy. Lets says you have a method that you're handling large objects in memory, such as images. Well in that case, you don't really want to wait until the release pool is done to release your memory. In that case, you can create an autorelease pool, which will cause any objects that were autoreleased within the pool to be deallocated automatically. It'll be easier if i show you some code first:

-(void)handleImages:(NSString*)imagePaths
{
   for(NSString* path in imagePaths)
   {
      UIImage* img = [self loadImageAtPath:path];
      // Code to do something with image
   }
}

In the code above, loadImageAtPath is giving me an autoRelease UIImage object, so i'm not responsible for releasing it, but its not going to get released until sometime after this method is over. And I don't want that, b/c that image object is large. In this scenario, i'll use a NSAutoReleasePool and wrap my the code that creates and manipulates the image:

-(void)handleImages:(NSString*)imagePaths
{
   for(NSString* path in imagePaths)
   {
      NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
     
      UIImage* img = [self loadImageAtPath:path];
      // Code to do something with image
      
      [pool release];
   }
}

And just like that the image will be released when the pool is released. Pretty cool, right?  And actually, you can use @autoReleasePool{} instead now, which is slightly more efficient, plust it even works in ARC, as NSAutoReleasePool is invalid now. 

So, now hopefully you have a grasp on how memory management works in objective-c. But like i said in the beginning, you actually don't have to do any of this. When you create an application now, xCode pops up a dialog:

Just make sure you have the "Use Automatic Reference Counting" checkbox checked, and you're good to go. By choosing this option, you can basically forget about everything that i talked about above :). 

In fact, if you have ARC turned on, you literally can no longer use "retain", "release", "autoRelease", "dealloc", and "NSAutoReleasePool".  If you do, the compiler will raise an error.  Thats b/c ARC is smart enough to figure out where to put the retain, release, and autoRelease calls into your code automatically at compile time. Pretty cool, right? And, like i said before, you scan still use the autoReleasePool, you just have to use it in a different syntax: 

-(void)handleImages:(NSString*)imagePaths
{
   for(NSString* path in imagePaths)
   {
      @autoreleasepool
      {
         UIImage* img = [self loadImageAtPath:path];
         // Code to do something with image
      }
   }
}

I hope you found this useful. 

You can read more from this series by checking out my blog

By Stephen Zaharuk (SteveZ)


Tags / ,