Ilya Suzdalnitski
Ilya Suzdalnitski

Reputation: 53540

Defining a constant in objective-c

I want to define a constant in objective-c.

Previously I had the following function:

+(NSString *) getDocumentsDir {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
    NSString *documentsDir = [paths objectAtIndex: 0];
    paths = nil;
    return documentsDir;
}

I'd like to define a constant "Documents_Dir" only once - when the function gets called and after that to access a previously created value.

I've tried the following code, which didn't work:

#define getDocumentsDir \
{   \
#ifdef Documents_Dir    \
return Documents_Dir;   \
#else   \
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);  \
NSString *documentsDir = [paths objectAtIndex: 0];  \
#define Documents_Dir [paths objectAtIndex: 0]; \
paths = nil;    \
return Documents_Dir;   \
#endif  \
}   \

I am not strong with precompiler directives, so any help will be appreciated.

Upvotes: 16

Views: 26782

Answers (3)

Cœur
Cœur

Reputation: 38667

Small optimization regarding Peter N Lewis code:

-(NSString *) documentsDir {
    static NSString *documentsDir = nil;
    return documentsDir ?: (documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]);
}

Upvotes: 1

Quinn Taylor
Quinn Taylor

Reputation: 44769

Prelude: It pays to understand the difference between precompiler directives and true constants. A #define just does a text replacement before the compiler builds the code. This works great for numerical constants and typedefs, but is not always the best idea for function or method calls. I'm operating under the assumption that you really want a true constant, meaning that the code to create the search path should only be executed once.


In your MyClass.m file, define the variable and populate it in an +initialize method like so:

static NSArray *documentsDir;

@implementation MyClass

+ (void) initialize {
    if (documentsDir == nil) {
        documentsDir = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES) lastObject] retain];
    }
}

...

@end

The static modifier makes it visible only within the compilation unit where it is declared. For a simple constant, this is all you need.

If the class has subclasses, +initialize will be called once for each subclass (by default), so you'll want to check whether documentsDir is nil before assigning to it, so you don't leak memory. (Or, as Peter Lewis points out, you can check if the class currently being initialized is the MyClass, using either == or the -isMemberOfClass: method.) If the subclasses also need to access the constant directly, you'd need to pre-declare the variable as extern in MyClass.h file (which the child classes include):

extern NSArray *documentsDir;

@interface MyClass : NSObject
...
@end

If you pre-declare the variable as extern, you must remove the static keyword from the definition to avoid compile errors. This is necessary so the variable can span multiple compilation units. (Ah, the joys of C...)

Note: In Objective-C code, the better way to declare something as extern is to use OBJC_EXPORT (a #define declared in <objc/objc-api.h>) which is set based on whether or not you're using C++. Just replace extern with OBJC_EXPORT and you're done.


Edit: I just happened upon a related SO question.

Upvotes: 34

Peter N Lewis
Peter N Lewis

Reputation: 17811

The easiest solution is to just change paths to be a static variable and evalutate it only once, like this:

+(NSString *) getDocumentsDir {
    static NSString *documentsDir = nil;
    if ( !documentsDir ) {
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
        documentsDir = [paths objectAtIndex: 0];
    }
    return documentsDir;
}

The "static" tells the compiler that documentsDir is effectively a global variable, although only accessible within the function. So it is initialized to nil, and the first call to getDocumentsDir will evalutate it and then further calls will return the pre-evalutated value.

Upvotes: 12

Related Questions