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


1002

Я розробляю програму Cocoa , і я використовую постійний NSStrings як спосіб зберігати ключові імена для моїх уподобань.

Я розумію, що це гарна ідея, оскільки вона дозволяє легко змінювати клавіші, якщо це необхідно.
Плюс, це все поняття "відокремити ваші дані від вашої логіки".

У будь-якому випадку, чи є хороший спосіб визначити ці константи один раз для всієї програми?

Я впевнений, що існує простий та розумний спосіб, але зараз мої заняття просто переосмислюють ті, якими вони користуються.


7
OOP - це згрупування даних за вашою логікою. Що ви пропонуєте - це лише хороша практика програмування, тобто полегшення зміни програми.
Раффі Хатчадуріан

Відповіді:


1287

Ви повинні створити файл заголовка, як

// 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 слід додати до цілі програми / рамки, щоб вона була пов'язана з кінцевим продуктом.

Перевага використання рядкових констант замість #define'd-констант полягає в тому, що ви можете перевірити рівність, використовуючи порівняння вказівників ( stringInstance == MyFirstConstant), яке набагато швидше порівняння рядків ( [stringInstance isEqualToString:MyFirstConstant]) (і простіше для читання, IMO).


67
Для цілої константи це було б: extern int const MyFirstConstant = 1;
Ден Морган

180
Загалом, чудова відповідь, з одним яскравим застереженням: НЕ хочеться перевіряти рівність рядків з оператором == в Objective-C, оскільки він перевіряє адресу пам'яті. Завжди використовуйте -isEqualToString: для цього. Ви можете легко отримати інший екземпляр, порівнявши MyFirstConstant і [NSString stringWithFormat: MyFirstConstant]. Не допускайте припущень щодо того, який екземпляр рядка у вас є, навіть з літералами. (У будь-якому випадку, #define - це "директива препроцесора", і замінюється перед компіляцією, тому будь-який спосіб компілятор бачить рядковий літерал у підсумку.)
Квінн Тейлор,

74
У цьому випадку добре використовувати == для перевірки рівності з постійною, якщо вона справді використовується як постійний символ (тобто використовується символ MyFirstConstant замість рядка, що містить @ "MyFirstConstant"). У цьому випадку замість рядка може бути використане ціле число (дійсно, це ви робите - використовуючи покажчик як ціле число), але використання постійної рядки робить налагодження дещо простішим, оскільки значення константи має читабельний для людини значення .
Barry Wark

17
+1 для "Constants.m слід додати до цілі програми / рамки, щоб вона була пов'язана з кінцевим продуктом." Зберегли мою здоровість. @amok, зробіть "Отримати інформацію" на Constants.m і виберіть вкладку "Цілі". Переконайтесь, що він перевіряється на відповідні цілі.
ПЕЗ

73
@Barry: У Какао я бачив ряд класів, які визначають їх NSStringвластивості copyзамість retain. Таким чином, вони можуть (і повинні) містити інший примірник вашої NSString*постійної, і порівняння прямих адрес пам'яті не зможе. Крім того, я б припустив, що будь-яка досить оптимальна реалізація -isEqualToString:перевірить рівність покажчиків перед тим, як потрапити в порівняння нітротистого характеру.
Бен Мошер

280

Найпростіший спосіб:

// 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";

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


12
Я думав, ви не повинні змінювати значення констант.
ruipacheco

71
Ендрю намагається змінити значення константи під час кодування, а не під час роботи програми.
Рандалл

7
Чи є якась додаткова цінність у виконанні extern NSString const * const MyConstant, тобто робить його постійним вказівником на постійний об'єкт, а не просто постійним вказівником?
Харі Карам Сінгх

4
Що станеться, якщо я використовую цю декларацію у файлі заголовка, статичний NSString * const kNSStringConst = @ "значення const"; Яка різниця між декларуванням та init окремо у файлах .h та .m?
Карим

4
@Dogweather - Де-небудь, де відповідь знає лише компілятор. IE, якщо ви хочете включити в меню приблизно про те, який компілятор був використаний для складання збірки програми, ви можете розмістити його там, оскільки компільований код інакше не міг би знати. Я не можу думати про багато інших місць. Макроси, безумовно, не слід використовувати в багатьох місцях. Що робити, якби у мене був #define MY_CONST 5 та в іншому місці #define MY_CONST_2 25. У результаті ви, можливо, дуже складно з помилкою компілятора, коли він намагатиметься компілювати 5_2. Не використовуйте #define для констант. Використовуйте const для констант.
ArtOfWarfare

190

Також слід згадати одне. Якщо вам потрібна не глобальна константа, ви повинні використовувати staticключове слово.

Приклад

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

Через staticключове слово цей const не видно поза файлом.


Незначна корекція за допомогою @QuinnTaylor : статичні змінні видимі в одиниці компіляції . Зазвичай це єдиний .m-файл (як у цьому прикладі), але він може вкусити вас, якщо ви оголосите його у заголовку, який міститься в іншому місці, оскільки після компіляції ви отримаєте помилки в посиланнях


41
Незначна корекція: статичні змінні видимі в одиниці компіляції . Зазвичай це єдиний .m-файл (як у цьому прикладі), але він може вкусити вас, якщо ви оголосите його у заголовку, який міститься в іншому місці, оскільки після компіляції ви отримаєте помилки в посиланнях.
Квінн Тейлор

Якщо я не використовую статичне ключове слово, чи kNSStringConst буде доступний протягом усього проекту?
Данял Айтекін

2
Гаразд, щойно перевірено ... Xcode не забезпечує автодоповнення його в інших файлах, якщо ви не статте вимкненим, але я спробував поставити те саме ім'я в двох різних місцях і відтворив помилки в посиланнях Quinn.
Данял Айтекін

1
статика у файлі заголовка не створює проблем із лінкером. Однак кожна одиниця компіляції, включаючи файл заголовка, отримає свою статичну змінну, тому ви отримаєте 100 з них, якщо включити заголовок із 100 .m файлів.
gnasher729

@kompozer У якій частині файлу .m ви розміщуєте це?
Василь Бурк

117

Прийнята (і правильна) відповідь говорить про те, що "ви можете включити цей файл [Constants.h] ... у попередньо складений заголовок проекту".

Як новачок у мене виникли труднощі робити це без додаткових пояснень - ось як: У файлі YourAppNameHere-Prefix.pch (це ім'я за замовчуванням для попередньо складеного заголовка в Xcode) імпортуйте свої константи.h всередину #ifdef __OBJC__блоку .

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

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


Я зробив це, але деякі файли помиляються під час компіляції "Використання незадекларованого ідентифікатора" CONSTANTSNAME "Якщо я включу в файл, який викидає помилку, постійна.h, вона працює, але це не те, що я хочу робити. Я очистив, відключив xcode та складання та проблеми все ще ... будь-які ідеї?
J3RM

50

Я, як правило, використовую спосіб, опублікований Баррі Варком та Рахулом Гуптою.

Хоча мені не подобається повторювати одні й ті ж слова у файлах .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 voila, я маю всю інформацію про константи лише у файлі .h.


Хм, є трохи застереження, однак, ви не можете використовувати таку методику, як ця, якщо файл заголовка імпортується в попередньо складений заголовок, оскільки він не завантажить .h файл у .m файл, оскільки він вже був складений. Однак є спосіб - дивіться мою відповідь (оскільки я не можу помістити приємний код у коментарях.
Scott Little

Я не можу це працювати. Якщо я поставлю #define SYNTHESIZE_CONSTS перед #import "myfile.h", він робить NSString * ... і в .h і .m (перевіряється за допомогою перегляду помічника та препроцесора). Він викидає помилки перегляду. Якщо я поставив його після #import "myfile.h", він робить зовнішній NSString * ... в обох файлах. Потім він викидає помилки "Не визначений символ".
арсеній

28

Я сам має заголовок, призначений для оголошення константних 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, який використовує рядок.


25

Невелика модифікація пропозиції @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. Для цього знадобиться перекомпіляція вашого ПК (і, можливо, всього проекту), коли будь-які значення змінюються, що не відбувається, якщо вони відокремлюються (і дублюються) як звичайні.

Сподіваюся, що комусь це корисно.


1
Використання #include виправило цей головний біль у мене.
Рамзель

Чи має це будь-яка продуктивність / втрата пам’яті порівняно з прийнятою відповіддю?
Гіфи

У відповіді на ефективність порівняно з прийнятою відповіддю немає. Це фактично те саме, що з точки зору компілятора. Ви закінчуєте ті ж декларації. Вони будуть точно такі ж, якби ви замінили externвищезазначене на FOUNDATION_EXPORT.
Скотт Літтл


12

Як сказав Абізер, ви можете помістити його у файл PCH. Ще один спосіб, який не є таким брудним, - це зробити файл включення для всіх своїх ключів, а потім або включити його у файл, у якому ви використовуєте ключі, або включити його до PCH. З ними у власний файл включено файл, який принаймні дає вам одне місце для пошуку та визначення всіх цих констант.


11

Якщо ви хочете чогось типу глобальних констант; Швидкий брудний спосіб - це введення постійних декларацій у pchфайл.


7
Редагування .pch зазвичай не найкраща ідея. Вам доведеться знайти місце для фактичного визначення змінної, майже завжди .m-файлу, тому має сенс оголосити її у відповідному .h-файлі. Прийнята відповідь про створення пари Constants.h / m є хорошою, якщо вони вам потрібні протягом усього проекту. Я, як правило, ставлю константи якомога далі вниз по ієрархії, виходячи з того, де вони будуть використовуватися.
Квінн Тейлор

8

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

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

Я інколи користуюсь цим.


6
Метод класу не є постійною. Він має вартість на час виконання, і не завжди може повернути той самий об’єкт (це буде, якщо ви реалізуєте його таким чином, але ви не обов'язково реалізували його таким чином), що означає, що вам доведеться використовувати isEqualToString:для порівняння, що є додаткові витрати на час виконання. Коли ви хочете константи, зробіть константи.
Пітер Хосей

2
@Peter Hosey, хоча ваші коментарі є правильними, ми вважаємо, що ця вистава потрапляє один раз на LOC або більше мовами "вищого рівня", як Ruby, не турбуючись про це. Я не кажу, що ти не правий, а просто коментую, наскільки стандарти різні в різних "світах".
Дан Розенстарк

1
Правда на Рубі. Більшість показників продуктивності, які люди кодують, є зовсім непотрібними для типового додатка.
Пітер ДеВіз

8

Якщо вам подобається константа простору імен, ви можете використовувати структуру, П'ятниця, Питання та відповіді 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"
};

1
Чудова річ! Але під ARC вам потрібно буде встановити всі змінні в структурі декларування за допомогою __unsafe_unretainedкласифікатора, щоб він працював.
Цемен

7

Я використовую однотонний клас, щоб я міг знущатися над класом і змінювати константи, якщо це необхідно для тестування. Клас констант виглядає приблизно так:

#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

1

Якщо ви хочете викликати щось подібне NSString.newLine;з об’єктивного c, і ви хочете, щоб воно було статичним постійним, ви можете швидко створити щось подібне:

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

І у вас є приємне для читання постійне визначення, яке доступне в межах типу на ваш вибір, при цьому стилі обмежені контекстом типу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.