views:

954

answers:

7

I'm somewhat confused by the following behavior I'm seeing within an Xcode project compiled for the iPhone simulator (or device):

NSString *test = [[NSString alloc] initWithCString:"foo"];

NSLog(@"test retain count = %d", [test retainCount]); // prints 1

[test release];

NSLog(@"test retain count = %d", [test retainCount]); // also prints 1 instead of 0

However, any further attempts to access 'test' result in a crash of the Xcode enviroinment, whether it be another [test retainCount] NSLog statement or otherwise (even if only to check if test is equal to nil).

Thoughts? Compiled within a simple View based test project...code exists within project's applicationDidFinishLaunching method.

Clarification -- I know NOT to do the above in practice. This was just a test to see why in some debugging cases a retain count of 1 wasn't actually reflecting the real state of an object. Thanks for your responses. This was just a test stub to see why I was seeing certain behavior in a few cases. What I'm really trying to do is track down a very small memory leak (0.06MB) that is consistently being created whenever I destroy/recreate a custom view.

+4  A: 

Retain counts are a debugging aid and can be misleading based on what Cocoa may be doing behind the scenes. This is particularly true with string literals where the data is permanently available and is never really deleted.

Concentrate of ensuring your code follows the Cocoa memory management rules with respect to object ownership. Where necessary, use Instruments to check for actual memory leaks.

Andrew Grant
Well this is precisely what I'm using the retain count for -- as a debugging aid. I am balancing my alloc calls, etc. I just wanted to inspect things further to help trace issues I'm having within a class dealloc() method.
abraginsky
A: 

Don't rely on retain counts...the framework may be retaining stuff on its own.

Instead, rely on the rules of memory management for objective-C. If you alloc, init, or new something, you own it and are responsible for releasing it. If you didn't, you aren't.

Genericrich
A: 

don't rely on the retain count; instead, make sure that you're balancing retains and releases. Here are some links on Cocoa (Touch) memory management: http://iamleeg.blogspot.com/2008/12/cocoa-memory-management.html

Graham Lee
+3  A: 

You are calling retainCount on test after it has been released and possibly deallocated, so definitely the result is not reliable, not to mention you shouldn't be sending dealloced objects any messages.

codelogic
This is insightful. What could conceptually be going on inside -release is `if(retainCount == 1) { /* dealloc myself */ }` without then updating the count ivar at all. The real point is not "cocoa is doing stuff behind the scenes" (which it also is, of course), but that "once that retain count falls to zero, absolutely all bets are off"
quixoto
A: 

When you send that release, the string is dealloced — it has no other owners retaining it. Sending further messages to it (including retainCount) is an error and won't return anything meaningful.

And as others have pointed out, don't rely on retain counts to be particularly useful. They're a piece of the picture you can look at, but they're often misleading. For example, autoreleases don't show up in the retain count, sometimes objects are retained extra times for caching purposes, etc. In this particular case, though, you're not sending the message to a valid object at all, so you're lucky to get back an answer rather than a crash.

You should read Apple's memory management guide if you're unclear on any of this. It's not too complicated and absolutely essential for programming Cocoa.

Chuck
A: 

One trick to improve the code shown above would be to use the 'set to nil' convention.

EG:

NSString *test = [[NSString alloc] initWithCString:"foo"];

NSLog(@"test retain count = %d", [test retainCount]); // prints 1

[test release]; test = nil; // set to nil, as I have released the pointer, I should no longer use it.

NSLog(@"test retain count = %d, test foobarpro = %d", [test retainCount], [test foobarPro]); // will now print 0 and 0 - because any objective-c call on a nil object returns 0 (and will not crash)

By setting a pointer to zero after you release it, you future proof your code a little: Someone coming along later and editing code 10 lines below can accidentally use 'test' with likely no ill effects (or an obvious crash). I even set pointers to nil in dealloc calls, as some of the hardest debugging tasks happen when non - nil 'bad' pointers are used in destruction.

Also you can debug with zombies to look for cases where you use pointers like your invalid test.

--Tom

Tom Andersen
A: 

Hi Tom,

thanks. This is helpful!

Should I always set any pointer to zero after I released it? My code takes care of releasing objects properly (at least I do not detect any leaks, however I do not set them to nil.

Thanks again!