views:

787

answers:

2

Consider the 2 following methods of reading a string from a file:

NSString *path = [[NSBundle mainBundle] pathForResource:@"foo" ofType:@"txt"];
NSString *string = [NSString stringWithContentsOfFile:path encoding:NSASCIIStringEncoding error:NULL];


NSString *path = [[NSBundle mainBundle] pathForResource:@"foo" ofType:@"txt"];
NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:path];
NSData *data = [file readDataToEndOfFile];
NSString *string = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
[file closeFile];

I would prefer to rely on method #1, but it behaves strangely when used in the following context:

NSString *string; // CLASS VARIABLE
(void) setupView
{
  string = ...; // LOADING THE STRING
}
(void) drawView
{
 ...;  // USING THE STRING
}

In short, it's an OpenGL-ES drawing-loop based on a NSTimer. The problem is that the string is accessible only at the first frame. At the next frame, the IPhone simulator (2.2) is crashing when trying to access the string.

Probably "something in my code, or in the OpenGL-ES code I'm using" one would say... But how to explain the strange fact that if I use method #2 to load the string, everything works as intended?

Any clues?

+1  A: 

When you create a String with a Class method generally it is added to the top most autorelease pool. At the end of the event loop the pool sends a release message to all objects it holds. In this case the newly created string has a retain count equal to 1 and at the end of the loop it reaches 0 and is deallocated. If you want to te keep the string send it a retain message to keep the retain count positive when the event loop ends.

Pedro Henriques
Oh, I see now! Reading more on the subject at http://stackoverflow.com/questions/6578/understanding-reference-counting-with-cocoa-objective-c
Ariel Malka
+2  A: 

In Cocoa, you only own an object if you create it by using alloc, new, copy or retain. Whenever you don't own an object, you have no guarantees about that object outside the local scope in which you use it. In your example, since you didn't create the new string with any of the above mentioned methods, you have no guarantee that it will be around later if you store it.

In order to take ownership of an object (i.e., that you want it to stick around so that you can use it later) that you don't already own (i.e., you didn't create it using one of the previously mentioned methods), you must send that object a retain message. For example, if you rewrite your code as shown below, you will own the string and you won't have to worry about using it later:

// option 1
NSString *path = [[NSBundle mainBundle] pathForResource:@"foo" ofType:@"txt"];
NSString *string = [[NSString alloc] initWithContentsOfFile:path encoding:NSASCIIStringEncoding error:NULL];

// option 2
NSString *path = [[NSBundle mainBundle] pathForResource:@"foo" ofType:@"txt"];
NSString *string = [NSString stringWithContentsOfFile:path encoding:NSASCIIStringEncoding error:NULL];
[string retain];

Option 1 is okay because you specifically create the string with the alloc message. You now own the string object until you relinquish ownership at some other time. This is probably the best option for you, given your particular circumstances (i.e., you don't really want to use the convenience constructor because you want the string object to hang around to be used later).

Option 2 is okay because you specifically take ownership of the string by sending it the retain. In your case, this probably isn't the best option because there is an alloc/init option available already and that's usually easier to read.

When you are done with an object and you want to relinquish ownership of the object, you send that object the release message. It's important to remember that in Cocoa, sending an object a release message doesn't mean that you're destroying it, just that you're relinquishing ownership.

Cocoa memory management is designed around the idea of ownership of objects. Although it may be a little confusing at first, once you wrap your head around it, it makes it a lot easier to program in the environment without introducing memory errors, leaks or other bugs. The conveniences methods (like stringWithString) are really designed to be used when you want to create an object that you're really only using for a brief period of time and within the scope of a single function or program block. If you plan to keep an object around past that scope, using the alloc/init or new methods to construct the object is preferred.

For more information, please read the Memory Management Programming Guide for Cocoa. It really is considered required reading for Cocoa developers. Also, it usually takes reading it, experimenting, and reading it a few more times to really grasp, especially if you have a lot of experience with other models of memory management.

Jason Coco