views:

187

answers:

3

I have a very clear question:

//.h file

@property (nonatomic, retain)NSMutableString * retainString;
@property (nonatomic, copy)NSMutableString * copyString;

//.m file
@synthesis retainString, copyString;
-(void)Process
{
  NSMutableString *test = [[NSMutableString alloc]inti];//retain count should be 1

  self.retainString = test;

  self.copyString = test;

}

cond. 1-> // retain count of both should be 2. As they are pointing to the same memory location with retain count 2 so how may release should be written.

cond. 2-> // //retain count of test is 1 and copyString is 2. As both hold different memory location. but can we write [copyString release].

+1  A: 

If you have properties defined with keywords 'retain' or 'copy' you should always release corresponding member variables in the dealloc method.

In this case your dealloc should look like this:

- (void)dealloc {
    [retainString release];
    [copyString release];

    [super dealloc];
}

Now, when you alloc a string in your custom class' method, this method owns the string as described in the Memory Management Programming Guide for Cocoa. This means that you should release the string before leaving the method.

@synthesize retainString, copyString;

- (void)Process {
    NSMutableString *test = [[NSMutableString alloc] init]; //retain count is 1

    self.retainString = test;     // retain count of test is 2
    self.copyString = test;       // test's retain count = 2, copyString's = 1

    [test release];               // retain count of test is 1 again
}

When an instance of this class is destroyed, the dealloc method will be invoked which will in turn release both strings. And as soon as their have retain counts remain 1, they will be dealloc'ed too.

Alex
+7  A: 

This setup actually does some very interesting things, and raises a couple good points about Objective-C memory management. Let's first reiterate the code:

// Testing.h
@interface Testing : NSObject {
    NSMutableString *retainString;
    NSMutableString *copyString;
}

@property(nonatomic,retain) NSMutableString *retainString;
@property(nonatomic,copy) NSMutableString *copyString;
// Testing.m
@implementation Testing

@synthesize retainString, copyString;

- (id)init {
    if(self = [super init]) {
        NSMutableString *test = [[NSMutableString alloc] init];
        NSLog(@"test %d; retain %d; copy %d", [test retainCount], [retainString retainCount], [copyString retainCount]);
        self.retainString = test;
        NSLog(@"test %d; retain %d; copy %d", [test retainCount], [retainString retainCount], [copyString retainCount]);
        self.copyString = test;
        NSLog(@"test %d; retain %d; copy %d", [test retainCount], [retainString retainCount], [copyString retainCount]);
        [self.copyString appendFormat:@"test"];
        NSLog(@"test %d; retain %d; copy %d", [test retainCount], [retainString retainCount], [copyString retainCount]);
    }
    return self;
}

@end

This produces the log output:

2009-12-24 03:35:01.408 RetainCountTesting[1429:40b] test 1; retain 0; copy 0
2009-12-24 03:35:01.410 RetainCountTesting[1429:40b] test 2; retain 2; copy 0
2009-12-24 03:35:01.410 RetainCountTesting[1429:40b] test 2; retain 2; copy 2147483647
2009-12-24 03:35:01.413 RetainCountTesting[1429:40b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendFormat:'

So what's going on here? The first two calls are fairly straightforward:

  • The initial call to alloc/init creates a new NSMutableString object with retain count 1, as expected. We have one object with one retain on it.
  • The assignment to the retained property increments the retain count, as expected. We have one object with two retains on it.

Here's where it gets strange. The assignment to the copy property does indeed make a copy, but not in the way you'd expect. NSString and NSMutableString are part of what's called a class cluster - when you create or modify a string, it may or may not be an instance of the class you're expecting. The language may mutate it to some other representation behind the scenes.

In this particular case, when the copy is performed, apparently the language decides that the string (since it contains no information) is to be considered immutable, and makes it so. This is often seen when people do something like [[NSString alloc] initWithString:@"hello"] - it's a constant, static string, so no object need be allocated dynamically. Keeping it static helps the runtime perform better.

So now we have two objects: our original test object that was retained twice, and the new object that is static and therefore has a retain count of INT_MAX. Finally, since the new string is immutable, calling a mutator method on it kills the program.

As an aside, changing the original call from init to initWithString: does make the copy assignment perform (somewhat) as expected - you only get a retain count of 1 on the copied object, but you still can't mutate it. Again, this is probably due to some optimization magic inside the compiler that decided that string was static and saw no reason to make it mutable if it didn't have to.

To answer your final question: yes, you can call release on either of these objects. It just won't do much. At best, you'll have destroyed the copied object (since it had a retain count of 1); at worst, it'll do nothing to a static string object. However, I'd recommend continuing to work through properties: rather than releasing the copied object, why not just do self.copyString = nil;? Since it calls the property setter, it'll take care of the release as necessary, and then you don't have a pointer to the object still floating around.

For more information on all this, consider reading:

Tim
Great answer, Tim.
gavinb
No need to go as far as attributing compiler optimizations for this case. The documentation states that the copy property attribute calls the copy method of the object. The NSCopying protocol that defines this method states that it will return an immutable object (if applicable for the class). To get a mutable copy, the property would have to call mutableCopy instead. Therefore, copy properties will always copy an immutable object and there's currently no way around this aside from implementing your own setter calling mutableCopy.
Adrian
Adrian: thanks for the info!
Tim