First off, it’s worthing clarifying that much of what is covered here is as much Cocoa as it is Objective-C. However, since the two are quite closely tied when it comes to developing Mac/iPhone applications, I’ve decided to keep with the Objective-C theme as it relates to the title of this and subsequent posts.
This post will review some of the nuances of memory management. I won’t go into all the details of memory management (see Objective-C Programming Guide , Cocoa Fundamentals and Memory Management Programming Guide for Cocoa ), rather, I’ll point out some of the more subtle concepts to keep in mind as you begin to work with objects.
Object Ownership
Rule #1 – If you create an object using alloc, new or a variant of copy (e.g. mutableCopy), you need to free (release or autorelease) that object. This also applies if you send a retain message to an object.
Rule #2 – If you didn’t create an object directly, don’t attempt to release the memory for the object.
For instance, here is an object that I “own” given that I’ve called ‘alloc’ to create the object. It’s up to me to release the memory for this object:
1 2 3 4 5 | |
Let’s look at two examples where we are not responsible for managing the memory associated with an object. First, when using a factory (aka convenience) methods of a class. For instance:
1 2 3 | |
The second example, is if you call an accessor method that returns an object. Assume that the object TestClass below has a getter method that returns a pointer to an instance variable named firstName that is an NSString.
1 2 3 | |
This makes sense if you think about it. The getter simple returns a reference (pointer) to an existing object. Now, if you want to retain (express an interest in the object to keep it around) then you would need to use ‘retain’ to bump the reference count. However, if we simply want a pointer to the object as shown here, we don’t have responsibility to release the object.
Initializing Objects
The order of things happening when initializing an object is of great relevance. For example, look at this block of code:
1 2 3 4 5 6 | |
Seems harmless enough, right? Allocate an object and send a message to initialize the object. Here’s the problem, if the init method returns nil (which all good initializers do upon failure to properly initialize an object), the code on line 5 would pass the test (that is, ptr would not be nil).
Now compare that with this approach:
1 2 3 4 5 | |
In the code above, assuming the init method returns nil upon failure, ptr will be set to nil, and the code on line 4 would not pass the test. Much better.
So, the lesson here is twofold: always return nil from an initializer method when the method fails to properly initialize the object. Second, it’s a good practice to combine a call to alloc and init into one step. We’ll talk more about initializers in a separate post.
Releasing Objects
Let’s talk about the other end, releasing objects. Have a look at the implementation for a simple class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
Look at the code below that creates an instance of the TestClass, calls the setter method to initialize the ’str’ instance variable, and releases the memory for the ‘ptr’ object.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
The dealloc method in TestClass releases the memory for the ’str’ instance variable, and it seems all is well. However, if at some point, somewhere inside the class implementation an attempt was made to check if the ’str’ instance variable is nil (prior to doing some operation) for example:
1 2 3 4 | |
the if statement would pass and the code on line 2 would be run. Not a good thing.
An alternative is to replace the call to release with a call to the setter method and pass in nil . Look at the dealloc method below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
The reason this works is that you can send a message to a nil object without raising an exception. If you follow the trail you’ll see inside the setStr method that the variable ‘input’ (which is nil) is sent a message to retain . This is effectively ignored, as the object is nil . The next line releases the string ’str’. The last line sets ’str’ to the value of the input parameter (which is nil ). This prevents the problem in the previous example where ’str’ still had a reference to a location in memory (even though the memory had been released).
As I was getting familiar with this, I wrote a short example in Xcode. Here is the test block for the dealloc method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Notice in this case that line 6 uses release. See the output below:
Now, if you comment out line 6 and remove the comment from line 9, you’ll get the following output. Notice how the reference to ’str’ after is 0 (nil).
You can download the Xcode project here if you’d like to take a closer look at the complete example. Hopefully this information will save you some time trying to track down an elusive memory leak.