views:

32

answers:

1

Hi all,

Mac OS X 10.6, Cocoa project, with retain/release gc

I've got a function which:

  • iterates over a specific directory, scans it for subfolders (included nested ones), builds an NSMutableArray of strings (one string per found subfolder path), and returns that array.

e.g. (error handling removed for brevity).

NSMutableArray * ListAllSubFoldersForFolderPath(NSString *folderPath)
{
    NSMutableArray *a = [NSMutableArray arrayWithCapacity:100];
    NSString *itemName = nil;
    NSFileManager *fm = [NSFileManager defaultManager];
    NSDirectoryEnumerator *e = [fm enumeratorAtPath:folderPath];

    while (itemName = [e nextObject]) {
        NSString *fullPath = [folderPath stringByAppendingPathComponent:itemName];
        BOOL isDirectory;
        if ([fm fileExistsAtPath:fullPath isDirectory:&isDirectory]) {
            if (isDirectory is_eq YES) {
                [a addObject: fullPath];
            }
        }           
    }
    return a;
}

The calling function takes the array just once per session, keeps it around for later processing:

static NSMutableArray *gFolderPaths = nil;

...

gFolderPaths = ListAllSubFoldersForFolderPath(myPath);
[gFolderPaths retain];

All appears good at this stage. [gFolderPaths count] returns the correct number of paths found, and [gFolderPaths description] prints out all the correct path names.

The problem:

When I go to use gFolderPaths later (say, the next run through my event loop) my assertion code (and gdb in Xcode) tells me that it is nil.

I am not modifying gFolderPaths in any way after that initial grab, so I am presuming that my memory management is screwed and that gFolderPaths is being released by the runtime.

My assumptions/presumptions

I do not have to retain each string as I add it to the mutable array because that is done automatically, but I do have to retain the the array once it is handed over to me from the function, because I won't be using it immediately. Is this correct?

Any help is appreciated.

+1  A: 

Objects do not “go nil”.

static NSMutableArray *gFolderPaths = nil;

This declaration declares that gFolderPaths is a variable that holds a pointer to an NSMutableArray object. You initialize it with a pointer to no object: nil.

This initialization is valid, and makes sense because you don't have an array to put here yet—better to initialize with the nil pointer than to not initialize and risk some random pointer being in the variable. (That can't happen with a static variable, as static variables are initialized to nil anyway, but explicitness is good and the explicit initialization is harmless.)

When I go to use gFolderPaths later (say, the next run through my event loop) my assertion code (and gdb in Xcode) tells me that it is nil.

I am not modifying gFolderPaths in any way after that initial grab, so I am presuming that my memory management is screwed and that gFolderPaths is being released by the runtime.

No. The runtime does not release objects. The runtime is part of the language, and retain and release are part of the Foundation framework. The framework sits just on top of the language.

So, you might guess that you or some other code (e.g., in a framework) released the object whose pointer you had previously stored in gFolderPaths.

No. If that had happened, the gFolderPaths variable would not suddenly contain nil; it would still contain the same pointer to the same object. If this were the last release before the object's death, the gFolderPaths variable would still contain the same pointer to the same, now-dead, object.

Attempting to log the pointer (e.g., with NSLog(@"%p", gFolderPaths)) would print a valid-looking address, such as 0x2381ab6780. Attempting to log the object (e.g., with %@) would almost certainly crash, because the object is dead.

That's not what happened. You said that your assertion and your commands to the debugger revealed that the gFolderPaths variable contains nil.

There are two obvious possibilities:

  1. Something re-assigned to the variable. You say that no code of yours reassigns to the variable. Nothing else should know about it, so this possibility is extremely unlikely.
  2. You never assigned an object's pointer to the variable in the first place. Either you assigned nil, or you never assigned anything. You say that you're logging the array whose pointer you assigned to the variable, and that the description checks out, so we can dismiss this possibility entirely. (Logging the count would not be so reliable a test, as [nil count] will successfully return 0.)

That leads to a third possibility:

3. You have two gFolderPaths variables.

I'm guessing you have two functions or methods (or one of each) that both contain this line:

static NSMutableArray *gFolderPaths = nil;

That won't work. Both gFolderPaths variables are static, but also local to the function/method you declared them in. Each function/method gets its own gFolderPaths variable, so you have two such variables, separate from each other.

You need to declare gFolderPaths as a static global variable, outside of any function or method. Better yet, if it is only being accessed from instances, make it an instance variable. Either way, it cannot be a local variable if you want to share it between two functions or methods.

The other way this could happen is if you have two such global declarations, but each in a different file. static on a variable declared at file scope means “only visible within this file”, so this causes the same problem: Two separate variables when you mean to have one shared variable. If this is your problem, the immediate fix is to remove the static keyword from both of them, but you should rethink your design if you mean to use a global variable in this way.

Peter Hosey
...and this is just one reason why global variables are a bad idea.
Alex
Thankyou very much Peter. You nailed it with answer #3: I actually had declared two 'gFolderPaths' because I'd copy-pasted the assignment. e.g. "NSMutableArray *gFolderPaths = ". OUCH! Been doing this for well over 20 years and I still get bitten by the basics.@ Alex, I use globals rarely. I use them with 'static' where appropriate. 99% of my data access across modules is done through a well documented, regimented API.Cheers.
SirRatty