Константы в Objective-C

Я разрабатываю приложение Какао , и я использую константы NSStrings как способы хранения имен ключей для моих предпочтений.

Я понимаю, что это хорошая идея, потому что она позволяет легко менять ключи при необходимости.
Плюс, это целое понятие «отделить ваши данные от вашей логики».

В любом случае, есть ли хороший способ сделать эти константы определенными один раз для всего приложения?

Я уверен, что есть простой и разумный способ, но сейчас мои классы просто переопределяют те, которые они используют.

11.02.2009 21:52:02
ООП - это группировка ваших данных с вашей логикой. То, что вы предлагаете, это просто хорошая практика программирования, то есть облегчение изменения вашей программы.
Raffi Khatchadourian 13.12.2011 01:01:37
14 ОТВЕТОВ
РЕШЕНИЕ

Вы должны создать заголовочный файл как

// Constants.h
FOUNDATION_EXPORT NSString *const MyFirstConstant;
FOUNDATION_EXPORT NSString *const MySecondConstant;
//etc.

(вы можете использовать externвместо, FOUNDATION_EXPORTесли ваш код не будет использоваться в смешанных средах C / C ++ или на других платформах)

Вы можете включить этот файл в каждый файл, который использует константы или в предварительно скомпилированный заголовок для проекта.

Вы определяете эти константы в файле .m как

// Constants.m
NSString *const MyFirstConstant = @"FirstConstant";
NSString *const MySecondConstant = @"SecondConstant";

Constants.m должен быть добавлен к цели вашего приложения / фреймворка, чтобы он был связан с конечным продуктом.

Преимущество использования строковых констант вместо #defined-констант состоит в том, что вы можете проверить на равенство, используя сравнение указателей ( stringInstance == MyFirstConstant), которое намного быстрее, чем сравнение строк ( [stringInstance isEqualToString:MyFirstConstant]) (и легче для чтения, IMO).

1284
29.04.2013 11:31:38
Для целочисленной константы это будет: extern int const MyFirstConstant = 1;
Dan Morgan 15.02.2009 17:20:46
В целом, отличный ответ, с одним очевидным предостережением: вы НЕ хотите проверять равенство строк с помощью оператора == в Objective-C, поскольку он проверяет адрес памяти. Всегда используйте -isEqualToString: для этого. Вы можете легко получить другой экземпляр, сравнивая MyFirstConstant и [NSString stringWithFormat: MyFirstConstant]. Не делайте предположений о том, какой у вас экземпляр строки, даже с литералами. (В любом случае, #define является «директивой препроцессора» и подставляется перед компиляцией, так что в любом случае компилятор видит строковый литерал в конце.)
Quinn Taylor 30.06.2009 02:21:24
В этом случае можно использовать == для проверки на равенство с константой, если она действительно используется в качестве константного символа (т.е. используется символ MyFirstConstant вместо строки, содержащей @ "MyFirstConstant"). В этом случае вместо строки можно использовать целое число (на самом деле, это то, что вы делаете - используя указатель в качестве целого числа), но использование константной строки делает отладку немного проще, так как значение константы имеет понятное для человека значение ,
Barry Wark 30.06.2009 15:48:47
+1 для «Constants.m должен быть добавлен к цели вашего приложения / фреймворка, чтобы он был связан с конечным продуктом». Спасло мое здравомыслие. @amok, сделайте «Получить информацию» на Constants.m и выберите вкладку «Цели». Убедитесь, что он проверен на соответствующие цели.
PEZ 19.10.2010 19:03:14
@ Барри: В Какао я видел несколько классов, которые определяют свои NSStringсвойства copyвместо retain. Как таковые, они могут (и должны) содержать другой экземпляр вашей NSString*константы, и прямое сравнение адресов памяти будет неудачным. Кроме того, я бы предположил, что любая разумно оптимальная реализация -isEqualToString:проверяет равенство указателей, прежде чем приступить к тщательному сравнению символов.
Ben Mosher 10.06.2011 11:45:20

Если вы хотите что-то вроде глобальных констант; Быстрый грязный способ - поместить объявления констант в pchфайл.

11
7.01.2017 09:04:36
Редактирование .pch обычно не лучшая идея. Вам нужно будет найти место для фактического определения переменной, почти всегда файла .m, поэтому имеет смысл объявить его в соответствующем файле .h. Принятый ответ на создание пары Constants.h / m будет хорошим, если они понадобятся вам во всем проекте. Обычно я помещаю константы как можно дальше вниз по иерархии в зависимости от того, где они будут использоваться.
Quinn Taylor 30.06.2009 02:27:34

Самый простой способ:

// Prefs.h
#define PREFS_MY_CONSTANT @"prefs_my_constant"

Лучший путь:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

// Prefs.m
NSString * const PREFS_MY_CONSTANT = @"prefs_my_constant";

Одним из преимуществ второго является то, что изменение значения константы не приводит к перестройке всей вашей программы.

280
11.02.2009 22:42:14
Я думал, что вы не должны менять значение констант.
ruipacheco 28.12.2009 12:38:53
Эндрю ссылается на изменение значения константы во время кодирования, а не во время работы приложения.
Randall 27.02.2010 11:02:22
Есть ли какая-то дополнительная ценность extern NSString const * const MyConstant, т. Е. Сделать ее постоянным указателем на постоянный объект, а не просто постоянным указателем?
Hari Karam Singh 1.03.2012 13:46:12
Что произойдет, если я использую это объявление в заголовочном файле, static NSString * const kNSStringConst = @ "const value"; В чем разница между отсутствием объявления и инициализации в файлах .h и .m?
karim 8.03.2012 13:06:23
@Dogweather - Где-то, где только компилятор знает ответ. То есть, если вы хотите включить в меню about информацию о том, какой компилятор использовался для компиляции сборки приложения, вы можете поместить его туда, поскольку скомпилированный код в противном случае не будет знать. Я не могу думать о многих других местах. Макросы, конечно, не должны использоваться во многих местах. Что, если бы у меня был #define MY_CONST 5 и в других местах #define MY_CONST_2 25. В результате вы можете получить ошибку компилятора, когда он попытается скомпилировать 5_2. Не используйте #define для констант. Используйте const для констант.
ArtOfWarfare 24.04.2013 14:03:24

Как сказал Abizer, вы можете поместить его в файл PCH. Другой способ, который не так грязен, - сделать включаемый файл для всех ваших ключей, а затем либо включить его в файл, в котором вы используете ключи, либо включить его в PCH. Имея их в отдельном включаемом файле, вы, по крайней мере, получаете одно место для поиска и определения всех этих констант.

12
11.02.2009 22:05:55

Есть также одна вещь, чтобы упомянуть. Если вам нужна не глобальная константа, вы должны использовать staticключевое слово.

пример

// In your *.m file
static NSString * const kNSStringConst = @"const value";

Из-за staticключевого слова этот констант не виден вне файла.


Незначительная коррекция @QuinnTaylor : статические переменные видны в модуле компиляции . Обычно это один файл .m (как в этом примере), но он может укусить вас, если вы объявите его в заголовке, который включен в другом месте, так как вы получите ошибки компоновщика после компиляции

190
23.05.2017 12:10:26
Незначительная коррекция: статические переменные видны в модуле компиляции . Обычно это один файл .m (как в этом примере), но он может укусить вас, если вы объявите его в заголовке, который включен в другом месте, так как вы получите ошибки компоновщика после компиляции.
Quinn Taylor 30.06.2009 02:22:58
Если я не использую ключевое слово static, будет ли kNSStringConst доступен на протяжении всего проекта?
Danyal Aytekin 7.11.2011 12:31:14
Хорошо, только что проверил ... Xcode не обеспечивает автозаполнение для него в других файлах, если вы оставите статический выключенный, но я попытался поместить одно и то же имя в двух разных местах и ​​воспроизвел ошибки компоновщика Quinn.
Danyal Aytekin 7.11.2011 13:47:32
static в заголовочном файле не создает проблем с компоновщиком Однако каждый модуль компиляции, включая файл заголовка, получит свою собственную статическую переменную, поэтому вы получите 100 из них, если включите заголовок из 100 .m файлов.
gnasher729 14.04.2014 10:55:48
@kompozer В какую часть файла .m вы помещаете это?
Basil Bourque 22.05.2014 19:25:35

Попробуйте использовать метод класса:

+(NSString*)theMainTitle
{
    return @"Hello World";
}

Я использую это иногда.

8
31.01.2016 17:55:33
Метод класса не является константой. Он имеет стоимость во время выполнения и может не всегда возвращать один и тот же объект (он будет, если вы реализуете его таким образом, но вы не обязательно реализовали его таким образом), что означает, что вы должны использовать isEqualToString:для сравнения, которое дополнительная стоимость во время выполнения. Когда вы хотите константы, делайте константы.
Peter Hosey 6.12.2009 15:51:06
@Peter Hosey, в то время как ваши комментарии верны, мы принимаем это снижение производительности один раз за LOC или больше на языках «высокого уровня», таких как Ruby, не беспокоясь об этом. Я не говорю, что вы не правы, а просто комментирую, как стандарты различны в разных «мирах».
Dan Rosenstark 17.01.2011 20:59:18
Правда на Руби. Большая часть производительности, которую кодируют люди, совершенно не нужна для типичного приложения.
Peter DeWeese 25.05.2011 21:20:04

В принятом (и правильном) ответе говорится, что «вы можете включить этот файл [Constants.h] ... в предварительно скомпилированный заголовок проекта».

Как новичок, мне было трудно сделать это без дальнейшего объяснения - вот как: в вашем файле YourAppNameHere-Prefix.pch (это имя по умолчанию для предварительно скомпилированного заголовка в XCode), импортируйте ваш Constants.h внутри #ifdef __OBJC__блока .

#ifdef __OBJC__
  #import <UIKit/UIKit.h>
  #import <Foundation/Foundation.h>
  #import "Constants.h"
#endif

Также обратите внимание, что файлы Constants.h и Constants.m не должны содержать в себе абсолютно ничего, кроме того, что описано в принятом ответе. (Нет интерфейса или реализации).

117
31.01.2016 17:56:33
Я сделал это, но некоторые файлы выдают ошибку при компиляции "Использование необъявленного идентификатора 'CONSTANTSNAME' Если я включаю константу.h в файл, выдающий ошибку, это работает, но это не то, что я хочу сделать. Я очистил, завершение работы xcode и build и еще проблемы ... есть идеи?
J3RM 30.08.2012 20:35:41
// Prefs.h
extern NSString * const RAHUL;

// Prefs.m
NSString * const RAHUL = @"rahul";
14
28.09.2011 16:19:44

Я обычно использую способ Барри Уорка и Рахула Гупты.

Хотя я не люблю повторять одни и те же слова в файлах .h и .m. Обратите внимание, что в следующем примере строка практически идентична в обоих файлах:

// file.h
extern NSString* const MyConst;

//file.m
NSString* const MyConst = @"Lorem ipsum";

Поэтому мне нравится использовать некоторые препроцессорные механизмы на языке C. Позвольте мне объяснить на примере.

У меня есть заголовочный файл, который определяет макрос STR_CONST(name, value):

// StringConsts.h
#ifdef SYNTHESIZE_CONSTS
# define STR_CONST(name, value) NSString* const name = @ value
#else
# define STR_CONST(name, value) extern NSString* const name
#endif

В моей паре .h / .m, где я хочу определить константу, я делаю следующее:

// myfile.h
#import <StringConsts.h>

STR_CONST(MyConst, "Lorem Ipsum");
STR_CONST(MyOtherConst, "Hello world");

// myfile.m
#define SYNTHESIZE_CONSTS
#import "myfile.h"

et вуаля, у меня есть вся информация о константах только в .h файле.

50
6.01.2012 01:17:37
Хм, есть небольшая оговорка, однако, вы не можете использовать эту технику, подобную этой, если файл заголовка импортируется в предварительно скомпилированный заголовок, потому что он не загрузит файл .h в файл .m, потому что он уже скомпилирован. Хотя есть способ - посмотрите мой ответ (так как я не могу поместить хороший код в комментарии.
Scott Little 2.12.2011 23:43:14
Я не могу заставить это работать. Если я помещаю #define SYNTHESIZE_CONSTS перед #import "myfile.h", он выполняет NSString * ... как в .h, так и в .m (проверено с использованием представления помощника и препроцессора). Выдает ошибки переопределения. Если я ставлю его после #import "myfile.h", он выполняет extern NSString * ... в обоих файлах. Затем выдает ошибки «Неопределенный символ».
arsenius 24.01.2014 16:23:03

Небольшая модификация предложения @Krizz, чтобы он работал должным образом, если заголовочный файл констант должен быть включен в PCH, что довольно нормально. Поскольку оригинал импортируется в PCH, он не будет перезагружать его в .mфайл, и поэтому вы не получите никаких символов, а компоновщик недоволен.

Однако следующая модификация позволяет ему работать. Это немного запутанно, но это работает.

Вам понадобятся 3 файла, .hфайл с постоянными определениями, .hфайл и .mфайл, который я буду использовать ConstantList.h, Constants.hи Constants.m, соответственно. содержание Constants.hпросто:

// Constants.h
#define STR_CONST(name, value) extern NSString* const name
#include "ConstantList.h"

и Constants.mфайл выглядит так:

// Constants.m
#ifdef STR_CONST
    #undef STR_CONST
#endif
#define STR_CONST(name, value) NSString* const name = @ value
#include "ConstantList.h"

Наконец, в ConstantList.hфайле есть реальные объявления, и это все:

// ConstantList.h
STR_CONST(kMyConstant, "Value");

Несколько вещей, чтобы отметить:

  1. Мне пришлось переопределить макрос в .mфайле после того, #undef как он будет использован.

  2. Мне также пришлось использовать #includeвместо #importэтого, чтобы это работало должным образом и чтобы компилятор не видел ранее скомпилированные значения.

  3. Это потребует перекомпиляции вашего PCH (и, вероятно, всего проекта) всякий раз, когда какие-либо значения будут изменены, что не имеет место, если они разделены (и дублированы) как обычно.

Надеюсь, что это полезно для кого-то.

25
3.12.2011 00:03:57
Использование #include исправило эту головную боль для меня.
Ramsel 3.04.2013 07:21:13
Имеет ли это какую-либо производительность / потерю памяти по сравнению с принятым ответом?
Gyfis 31.05.2015 13:23:50
В ответ на производительность по сравнению с принятым ответом, нет ни одного. Фактически это то же самое с точки зрения компилятора. Вы в конечном итоге с такими же заявлениями. Они были бы точно такими же, если бы вы заменили externвышеуказанное на FOUNDATION_EXPORT.
Scott Little 12.08.2015 15:29:32

Я использую одноэлементный класс, так что я могу издеваться над классом и изменять константы, если это необходимо для тестирования. Класс констант выглядит так:

#import <Foundation/Foundation.h>

@interface iCode_Framework : NSObject

@property (readonly, nonatomic) unsigned int iBufCapacity;
@property (readonly, nonatomic) unsigned int iPort;
@property (readonly, nonatomic) NSString * urlStr;

@end

#import "iCode_Framework.h"

static iCode_Framework * instance;

@implementation iCode_Framework

@dynamic iBufCapacity;
@dynamic iPort;
@dynamic urlStr;

- (unsigned int)iBufCapacity
{
    return 1024u;
};

- (unsigned int)iPort
{
    return 1978u;
};

- (NSString *)urlStr
{
    return @"localhost";
};

+ (void)initialize
{
    if (!instance) {
        instance = [[super allocWithZone:NULL] init];
    }
}

+ (id)allocWithZone:(NSZone * const)notUsed
{
    return instance;
}

@end

И он используется следующим образом (обратите внимание на использование сокращения для констант c - он сохраняет [[Constants alloc] init]каждый раз при наборе ):

#import "iCode_FrameworkTests.h"
#import "iCode_Framework.h"

static iCode_Framework * c; // Shorthand

@implementation iCode_FrameworkTests

+ (void)initialize
{
    c  = [[iCode_Framework alloc] init]; // Used like normal class; easy to mock!
}

- (void)testSingleton
{
    STAssertNotNil(c, nil);
    STAssertEqualObjects(c, [iCode_Framework alloc], nil);
    STAssertEquals(c.iBufCapacity, 1024u, nil);
}

@end
7
16.07.2012 00:00:35

У меня сам есть заголовок, предназначенный для объявления констант NSStrings, используемых для таких настроек:

extern NSString * const PPRememberMusicList;
extern NSString * const PPLoadMusicAtListLoad;
extern NSString * const PPAfterPlayingMusic;
extern NSString * const PPGotoStartupAfterPlaying;

Затем объявите их в сопроводительном файле .m:

NSString * const PPRememberMusicList = @"Remember Music List";
NSString * const PPLoadMusicAtListLoad = @"Load music when loading list";
NSString * const PPAfterPlayingMusic = @"After playing music";
NSString * const PPGotoStartupAfterPlaying = @"Go to startup pos. after playing";

Этот подход хорошо послужил мне.

Изменить: Обратите внимание, что это работает лучше всего, если строки используются в нескольких файлах. Если его использует только один файл, вы можете просто сделать это #define kNSStringConstant @"Constant NSString"в файле .m, который использует строку.

28
26.08.2013 04:22:08

Если вам нравится константа пространства имен, вы можете использовать struct, Friday Q & A 2011-08-19: Константы и функции пространства имен

// in the header
extern const struct MANotifyingArrayNotificationsStruct
{
    NSString *didAddObject;
    NSString *didChangeObject;
    NSString *didRemoveObject;
} MANotifyingArrayNotifications;

// in the implementation
const struct MANotifyingArrayNotificationsStruct MANotifyingArrayNotifications = {
    .didAddObject = @"didAddObject",
    .didChangeObject = @"didChangeObject",
    .didRemoveObject = @"didRemoveObject"
};
8
12.08.2016 11:54:11
Отличная вещь! Но в ARC вам нужно будет префикс всех переменных в объявлении структуры с __unsafe_unretainedквалификатором, чтобы он заработал.
Cemen 2.08.2015 06:20:02

Если вы хотите вызвать что-то подобное NSString.newLine;из цели c и хотите, чтобы это было статической константой, вы можете создать что-то вроде этого в swift:

public extension NSString {
    @objc public static let newLine = "\n"
}

И у вас есть хорошее читаемое определение константы, доступное из любого типа по вашему выбору, в то время как стиль ограничен контекстом типа.

1
21.01.2019 17:17:05