Определение константы в objective-c

Я хочу определить константу в objective-c.

Раньше у меня была следующая функция:

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

Я хотел бы определить константу "Documents_Dir" только один раз - когда функция вызывается и после этого открывается ранее созданное значение.

Я пробовал следующий код, который не работал:

#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  \
}   \

Я не уверен в директивах прекомпилятора, поэтому любая помощь будет оценена.

Ответ 1

Прелюдия:. Платит понять разницу между директивами прекомпилятора и истинными константами. A #define просто выполняет замену текста, прежде чем компилятор создает код. Это отлично работает для числовых констант и typedefs, но не всегда является лучшей идеей для вызовов функций или методов. Я работаю в предположении, что вам действительно нужна истинная константа, а это значит, что код для создания пути поиска должен выполняться только один раз.


В файле MyClass.m определите переменную и заполните ее в методе +initialize, например:

static NSArray *documentsDir;

@implementation MyClass

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

...

@end

Модификатор static делает его видимым только внутри единицы компиляции, где он объявлен. Для простой константы это все, что вам нужно.

Если класс имеет подклассы, +initialize будет вызываться один раз для каждого подкласса (по умолчанию), поэтому вы должны проверить, есть ли documentsDir nil перед его назначением, поэтому вы не просачиваетесь Память. (Или, как указывает Питер Льюис, вы можете проверить, является ли класс, который в настоящее время инициализируется, MyClass, используя либо ==, либо -isMemberOfClass:.) Если подклассам также необходимо напрямую обращаться к константе, вам нужно предварительно объявить переменную как extern в файле MyClass.h (к которому относятся дочерние классы):

extern NSArray *documentsDir;

@interface MyClass : NSObject
...
@end

Если вы предварительно объявляете переменную как extern, вы должны удалить ключевое слово static из определения, чтобы избежать ошибок компиляции. Это необходимо, чтобы переменная могла охватывать несколько единиц компиляции. (Ах, радости С...)

Примечание: В коде Objective-C лучший способ объявить что-то как extern - использовать OBJC_EXPORT (a #define, объявленный в <objc/objc-api.h>), который задается в зависимости от того, используете ли вы С++. Просто замените extern на OBJC_EXPORT, и все готово.


Изменить: Я только что случилось с связанным вопросом SO.

Ответ 2

Самое простое решение - просто изменить пути как статическую переменную и evalutate это только один раз, например:

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

"static" сообщает компилятору, что documentDir - это фактически глобальная переменная, хотя она доступна только внутри функции. Таким образом, он инициализируется нулем, и первый вызов getDocumentsDir будет его эвакуировать, а затем дальнейшие вызовы возвратят предварительно эвакуированное значение.

Ответ 3

Небольшая оптимизация в отношении кода Питера Н. Льюиса:

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