Bill Burgess
Bill Burgess

Reputation: 14164

Explain alloc/init occurring twice

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.

Upvotes: 2

Views: 1424

Answers (3)

bbum
bbum

Reputation: 162722

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".

Upvotes: 27

morningstar
morningstar

Reputation: 9132

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

Upvotes: 2

filipe
filipe

Reputation: 3380

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.

Upvotes: 11

Related Questions