2
votes

I realize this question may sound dumb, but just bear with me. I built an app to help new developers wrap their head around memory retention on the iPhone (no ARC yet). It is plain and simple, 4 buttons, init, access, retain, and release. Pretty self explanatory. I am displaying what the retain count for my string object that is the target of our poking and prodding. (Please no lectures on use of [myVar retainCount], I already know)

This stuff will never make it into actual apps, just toying with it for fun and hopefully help someone learn how memory works. My retain and release all work great. My question is that why does my retain count drop back to 1 if I call myString = [[NSMutableString alloc] init]; again. I can boost my retain count to 40, but after calling alloc/init I go back to zero. I am not leaking anywhere, just curious what happens to myString if/when alloc/init is called on it again.

3
Displaying the retain count, even in this limited of a context, is misleading. If you do limit the use to the point that the retain count is accurate and understandable, you have so limited the scope that it is no longer representative of real world memory management. Whatever your students might learn from this app will be useless when working on real apps. - bbum
Apparently you didn't read in the question, "Please no lectures on use of [myVar retainCount]." - morningstar
@morningstar - Using -retainCount near bbum is like waving a red flag in front of a bull. - Brad Larson
@morningstar The question itself has nothing to do with retainCount and the very fact that retainCount was brought into the mix indicated a lack of understanding of something very basic on Bill's part (no criticism intended -- we all started there). If you note, my answer doesn't actually mention retainCount until the last paragraph of the new part. As for the comment, well, it was a comment... not an answer. - bbum
Even if you understand retainCount fully and why it is misleading, using it to teach your students will teach them that retainCount is useful for learning and/or debugging. Better to remove the retainCount display in the app and use Instruments to view an object's history live as you hit the buttons: then you teach them a useful debugging technique and don't implicitly teach them to use retainCount. - Peter Hosey

3 Answers

28
votes

My question is that why does my retain count drop back to 1 if I call myString = [[NSMutableString alloc] init]; again?

Because you are failing to understand a very basic concept of Objective-C; myString is not an instance of an NSMutableString, but a reference to an instance. If you were to:

myString = [[NSMutableString alloc] init];
myString = [[NSMutableString alloc] init];

You now have two instances of NSMutableString, one leaked.

If you:

myString = [[NSMutableString alloc] init];
otherString = myString;

You now have a single instance of NSMutableString with two references.

In all three allocations, the NSMutableString instance will have a +1 retain count and, thus, you must balance each with a single release or you'll leak.

Treating retain counts as an absolute count is a path to madness. Or, at best, the scope of usefulness of the absolute retain count is so limited that learning about it is not applicable to real world iOS programming.


This bears repeating:

The retainCount of an object is tricky business.

If you were to continue down this path, you should be aware of the following details:

  • retainCount can never return 0
  • messaging a dangling pointer is not guaranteed to crash
  • retain count cannot be known once you have passed an object through any system API due to implementation details
  • any subclass of any system class may have an unknown retain count due to implementation details
  • retain count never reflects whether or not an object is autoreleased
  • autoreleases is effectively thread specific while the retain count is thread global
  • some classes are implemented with singletons some of the time (NSString, certain values of NSNumber)
  • the implementation details change from platform to platform and release to release
  • attempting to swizzle retain/release/autorelease won't work as some classes don't actually use those methods to maintain the retain count (implementation detail, changes per platform/release, etc..)

If you are going to teach retain/release, you should be treating the retain count as a delta and focus entirely on "If you increase the RC, you must decrease it".

11
votes

when you call myString = [[NSMutableString alloc] init];, you're not "calling alloc/init on it again". You're not calling a method on the same instance you had. You're allocating and initializing a new instance, a completely different object from the one you had before.

And if you're doing that with a variable that had an object that you retained, then yes, you are leaking it.

2
votes

Try this.

NSString *myString = [[NSMutableString alloc] init];
NSLog(@"%d", [myString retainCount]); // "1"
for (int i = 1; i < 40; i++)
    [myString retain];
NSLog(@"%d", [myString retainCount]); // "40"

NSString *backup = myString;
myString = [[NSMutableString alloc] init];
NSLog(@"%d", [myString retainCount]); // "1"
NSLog(@"%d", [backup retainCount]); // "40"

You see, you have a different object with a new retain count. Your original object still exists and still has the same retain count. Assignment changes the object a variable refers to. A variable doesn't have a retain count, an object does.

myString = someOtherString;
NSLog(@"%d", [myString retainCount]); // who knows?

With retained property:

self.iString = backup;
NSLog(@"%d", [self.iString retainCount]); // "41" - 1 more because property retained
NSString *newString = [[NSMutableString alloc] init];
NSLog(@"%d", [newString retainCount]); // "1"
self.iString = newString;
// 1 for alloc 1 for retain (in real code you should release newString next)
NSLog(@"%d", [self.iString retainCount]); // "2"
NSLog(@"%d", [backup retainCount]); // "40" - self.iString released it