Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 658
Ilya Suzdalnitski Avatar asked Jun 29 '09 18:06

Ilya Suzdalnitski


People also ask

How do you define a constant?

A constant is a value that cannot be altered by the program during normal execution, i.e., the value is constant. When associated with an identifier, a constant is said to be “named,” although the terms “constant” and “named constant” are often used interchangeably.

How do I use #define in Objective-C?

Where you typically used the #define directive to define a primitive constant in C and Objective-C, in Swift you use a global constant instead. For example, the constant definition #define FADE_ANIMATION_DURATION 0.35 can be better expressed in Swift with let FADE_ANIMATION_DURATION = 0.35.

How do you define a constant variable?

A constant variable is one whose value cannot be updated or altered anywhere in your program. A constant variable must be initialized at its declaration.

How do you assign a value to a variable in Objective-C?

In Objective-C, you always have to explicitly specify the type of variable you're declaring. In Swift, the “var” keyword is used to declare a variable and you don't have to specify the type if it can be inferred from what you're assigning it. Also notice in Swift, we're using the String class as opposed to NSString.


2 Answers

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.

like image 63
Quinn Taylor Avatar answered Oct 28 '22 03:10

Quinn Taylor


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.

like image 25
Peter N Lewis Avatar answered Oct 28 '22 04:10

Peter N Lewis