views:

76

answers:

1

I am learning Objective-C. I am trying to release all of the memory that I use. So, I wrote a program to test if I am doing it right:

#import <Foundation/Foundation.h>

#define DEFAULT_NAME @"Unknown"

@interface Person : NSObject
{
  NSString *name;
}
@property (copy) NSString * name;
@end

@implementation Person
@synthesize name;
- (void) dealloc {
  [name release];
  [super dealloc];
}
- (id) init {
  if (self = [super init]) {
    name = DEFAULT_NAME;
  }
  return self;
}
@end


int main (int argc, const char * argv[]) {
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  Person *person = [[Person alloc] init];
  NSString *str;
  int i;

  for (i = 0; i < 1e9; i++) {
    str = [NSString stringWithCString: "Name" encoding: NSUTF8StringEncoding];
    person.name = str;
    [str release];
  }

  [person release];
  [pool drain];
  return 0;
}

I am using a mac with snow leopard. To test how much memory this is using, I open Activity Monitor at the same time that it is running. After a couple of seconds, it is using gigabytes of memory. What can I do to make it not use so much?

+4  A: 

Firstly, your loop is incorrect. +stringWithCString:… is not an +alloc/+new…/-copy method, so you should not -release it.

Either one of these are correct:

  1. Don't -release:

    str = [NSString stringWithCString: "Name" encoding: NSUTF8StringEncoding];
    person.name = str;
    
  2. Use -init:

    str = [[NSString alloc] initWithCString: "Name" encoding: NSUTF8StringEncoding];
    person.name = str;
    [str release];
    

Similarly, in -[Person init]:

- (id) init {
  if ((self = [super init])) {
    name = [DEFAULT_NAME copy]; // <----
  }
  return self;
}

Now, if you use variant #1, the memory should rise up to gigabytes as you have seen before, while variant #2 should be a rather constant, small value.

The difference is because

str = [NSString stringWithCString: "Name" encoding: NSUTF8StringEncoding];

is equivalent to

str = [[[NSString alloc] initWithCString:......] autorelease];

An -autoreleased object means "transfer the ownership to the nearest NSAutoreleasePool, and let it release it later".

How late? By default, it's when the current run loop ticked once. However, you did not have an explicit run loop here*, so the run loop did not run. The autorelease pool never have a chance to clear up these 109 allocated temporary strings.

However, for variant #2, the temporary strings are immediately released, so the temporaries won't fill up the memory. (We don't need to wait for the pool to flush — there's no pools involved.)


Note:

*: A run loop is a unique loop attached to each running thread. If you write a CLI utility, there's seldom need to have a run loop.

KennyTM
Very helpful, thanks!
Adrian