Question

STOP PRESS OK before you see the word retainCount in the following question, please skip to the EDIT at the bottom where I have stated that I have stopped using it.

My Cocoa App, which uses MRR, creates many global resources, which I am loading in main(), before NSApplicationMain() is called. As NSApplicationMain() doesn't return, I have hooked the clean-up of these resources using atexit(), like this:

atexit(cleanup);

if (![CocoaUtil initCocoaUtil] ||
    ![PreferenceController initPreferenceController] ||
    ![ResourceManager initResourceManager])
{
    criticalAlertPanel(@"Failed to initialize application",
                       @"Failed to initialize application");
    return 4;
}

retval = NSApplicationMain(argc, (const char **)argv);

However cleanup() is getting called before any of the views in my NSDocument subclass are dealloc'd (I have lack of log message to show this) and hence the reference counts of the objects in the global resources is sometimes > 1. I am being over-cautious and attempting to pre-empt memory leaks by using this method to release my global resources:

+ (void)fullRelease:(id)obj
             format:(NSString *)format, ...
{
    if (obj == nil)
        return;

    NSUInteger retainCount = [obj retainCount];
    if (retainCount > 1)
    {
        va_list va;
        va_start(va, format);
        NSString *objDesc = [[NSString alloc] initWithFormat:format arguments:va];
        logwrn(@"%@ has a reference count of %lu", objDesc, retainCount);
        [objDesc release];
    }

    while (retainCount > 0)
    {
        [obj release];
        retainCount--;
    }
}

My log shows the following:

12:15:04.954 INF -[AppController applicationDidFinishLaunching:] Application launched
12:15:06.702 INF -[AppController applicationShouldTerminate:] Application terminating
12:15:06.703 INF -[AppController applicationWillTerminate:] Application terminating
12:15:06.705 DBG cleanup Cleaning-up
12:15:06.705 INF +[ResourceManager finiResourceManager] Cleaning up
12:15:06.709 WRN +[CocoaUtil fullRelease:format:] _images[2] has a reference count of 2
12:15:06.709 WRN +[CocoaUtil fullRelease:format:] _images[3] has a reference count of 2
12:15:06.709 WRN +[CocoaUtil fullRelease:format:] _images[4] has a reference count of 2
12:15:06.710 WRN +[CocoaUtil fullRelease:format:] _images[5] has a reference count of 2
12:15:06.710 WRN +[CocoaUtil fullRelease:format:] _images[6] has a reference count of 2
12:15:06.710 WRN +[CocoaUtil fullRelease:format:] _images[7] has a reference count of 2
12:15:06.711 WRN +[CocoaUtil fullRelease:format:] _images[8] has a reference count of 2
12:15:06.711 WRN +[CocoaUtil fullRelease:format:] _images[9] has a reference count of 2
12:15:06.721 DBG +[PreferenceController finiPreferenceController] Cleaning up
12:15:06.721 DBG +[CocoaUtil finiCocoaUtil] Cleaning up

My question (finally!) is:

Is there a way to ensure I clean-up my global resource after all the NSDocument instances have been destroyed and stop getting these false negatives?

EDIT: I have unhooked the fullRelease call and just performed a normal release on my resources and Instruments did not detect any memory leaks, so things are OK, but I am curious as to why the NSDocument objects don't appear to be being released before atexit() is called.

Was it helpful?

Solution

Do not release something you do not own!

Every retain belongs to somebody else. Only send release to an object to balance your calls to new, alloc, copy, or retain (NARC.) This sort of behaviour will inevitably cause a crash in production code.

It looks like you want to make sure an object is deallocated rather than simply taken care of. If you own the object, release it once; the other references to it belong to other objects. It's possible you do have memory leaks in your code (we can't tell just from this code sample) but those can usually be found with the Static Analyzer, Instruments, and a bit of elbow grease.

More importantly: the operating system will free all your memory for you when your process exits. This is not part of the C standard but it is simply how OS X and iOS operate, and it is the expected behaviour on other platforms that support Objective-C. So you don't have to do anything special to clean up when your process exits, except perhaps writing files to disk or similar. In fact, many Cocoa applications don't bother to release anything owned by their app delegates, because it's faster to let the operating system dump the memory than it is to call -release on thousands of objects.

Do not call -retainCount!

It lies. Plain and simple. It includes temporary references used by Cocoa, most importantly, and you should never attempt to interfere with those. -retainCount is a poisoned symbol.

OTHER TIPS

Some notes:

  • don't use retainCount; see the links on http://whentouseretaincount.com for lots of details

  • atexit() handlers are useless in high level programming. When called, the app is in a relatively undefined state. The frameworks will have torn down some stuff but, as you noted, there will be a ton of objects that will never be deallocated. atexit() may not be called at all, in some cases.

  • you cannot rely on app termination to do any kind of required state cleanup. The user may force quit your app. The system might, too, or it might be force-rebooted. At-termination behavior should be treated as an optimization; can you do some stuff that'll make the next launch faster.

  • during app termination, there is no need to deallocate anything. The system will reclaim all app resources upon termination, regardless of app state. In other words, objects left in memory at termination are not leaks.

  • in general, the "Leaks" detection tools can only be used to fix obvious problems. Leaks only detects memory leaks. It cannot detect memory accretion where the objects accreted are still connected to a global somehow. Accretion aren't technically leaks, but they can easily be a huge source of problems.

  • HeapShot analysis will detect both leaks and memory accretion.

  • Consider migrating your app to use ARC. One of the guiding patterns of ARC is that the compiler has complete knowledge of the lifespan of objects. Expressions that are ambiguous under MRR are disallowed under ARC (without proper markup, in some cases). As well, the ARC compiler and analyzer can do a much deeper analysis of your code, finding many issues that can be quite subtle.

Disable sudden termination, don't use globals, and just use the regular reference counting rules. In some cases, you will need to break strong circular references or manually clear out your objects' instance variables. Finally, it may be more helpful to pause/break before main returns, and run heap to see what's really alive (a leak) at this stage.

I am being over-cautious and attempting to pre-empt memory leaks by using this method to release my global resources.

You should not purge like this - you know better :)

However, locating and destroying all references/objects/allocations which you have control over is actually a very good practice, to ensure your programs work well, are reusable, and as good metrics for monitoring regressions as your apps change.

As all the others have said: Release only what you own. Your app may not crash for you now, but you're just getting lucky. It may crash on another Mac when you do this, or in two hours, or whatever.

If you have a scarce resource that some object is holding on to that really needs to be unregistered from (e.g. a network session to another server, where not correctly sending it a sign-off message will cause the server to wait for you, keeping some internal state for your return), you should build that into your objects instead of doing that when your objects are released.

E.g. there is a notification that Mac OS X sends before your application quits, NSApplicationWillTerminate. Have your object register for that, and when it gets it, have it send the server its goodbye (and remember that it already did that). That way, when your application quits, but e.g. the application delegate is still holding on to that object, you're still sure that the sign-off will happen.

But you should probably still also check if you've signed off already in -dealloc. One day you might add support for multiple servers, and then when you release one server, you want it to send a goodbye even though your app will not quit. But only do the sign off if you haven't done it already.

Same goes for when you have some data in memory that you want to write down before you quit (e.g. that's what NSUserDefaults does to make sure access to preferences is fast, but it still gets written to disk before you quit).

But cases like that are REALLY rare. Usually NSDocument will call you to write to disk when needed, and generally servers notice when the connection drops and clean up by themselves. So you really shouldn't release stuff that doesn't belong to you. You'll just cause crashes on quit for some of your users. It gives a really bad impression, and can cause your app to actually fail before it got the chance to save some data, and as such make things worse.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top