Stałe w celu C

1002

Zajmuję się Cocoa aplikacji i używam stale NSStrings jako sposobów przechowywania nazwy klawiszy do moich preferencji.

Rozumiem, że to dobry pomysł, ponieważ w razie potrzeby umożliwia łatwą zmianę kluczy.
Ponadto jest to całe pojęcie „oddziel swoje dane od logiki”.

W każdym razie, czy istnieje dobry sposób, aby zdefiniować te stałe raz dla całej aplikacji?

Jestem pewien, że istnieje prosty i inteligentny sposób, ale teraz moje klasy po prostu redefiniują te, których używają.

Allyn
źródło
7
OOP jest o grupowanie danych z logiki. To, co proponujesz, jest po prostu dobrą praktyką programistyczną, tj. Ułatwianiem zmiany programu.
Raffi Khatchadourian,

Odpowiedzi:

1287

Powinieneś utworzyć plik nagłówka jak

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

(możesz użyć externzamiast, FOUNDATION_EXPORTjeśli twój kod nie będzie używany w mieszanych środowiskach C / C ++ lub na innych platformach)

Możesz dołączyć ten plik do każdego pliku, który używa stałych lub we wstępnie skompilowanym nagłówku projektu.

Zdefiniujesz te stałe w pliku .m, takim jak

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

Constants.m należy dodać do celu aplikacji / frameworka, aby był powiązany z produktem końcowym.

Zaletą używania stałych ciągowych zamiast stałych #defined jest to, że można sprawdzić równość za pomocą wskaźnika porównania ( stringInstance == MyFirstConstant), który jest znacznie szybszy niż porównanie ciągu ( [stringInstance isEqualToString:MyFirstConstant]) (i łatwiejszy do odczytania, IMO).

Barry Wark
źródło
67
Dla stałej całkowitej byłoby to: extern int const MyFirstConstant = 1;
Dan Morgan
180
Ogólnie rzecz biorąc, świetna odpowiedź, z jednym jaskrawym zastrzeżeniem: NIE chcesz testować równości łańcuchów z operatorem == w Objective-C, ponieważ testuje on adres pamięci. Zawsze używaj do tego opcji -isEqualToString: Możesz łatwo uzyskać inną instancję, porównując MyFirstConstant i [NSString stringWithFormat: MyFirstConstant]. Nie przyjmuj żadnych założeń co do tego, jaki ciąg znaków masz, nawet z literałami. (W każdym razie #define jest „dyrektywą preprocesora” i jest zastępowane przed kompilacją, więc w obu przypadkach kompilator widzi na końcu dosłowny ciąg znaków.)
Quinn Taylor
74
W takim przypadku można użyć == do sprawdzenia równości ze stałą, jeśli naprawdę jest używany jako stały symbol (tzn. Używany jest symbol MyFirstConstant zamiast ciągu zawierającego @ „MyFirstConstant”). W tym przypadku zamiast łańcucha można użyć liczby całkowitej (tak naprawdę robisz - używanie wskaźnika jako liczby całkowitej), ale użycie stałego łańcucha znacznie ułatwia debugowanie, ponieważ wartość stałej ma znaczenie czytelne dla człowieka .
Barry Wark
17
+1 za „Constants.m należy dodać do celu aplikacji / frameworka, aby był powiązany z produktem końcowym”. Uratowałem moje zdrowie psychiczne. @amok, zrób „Uzyskaj informacje” w Constants.m i wybierz zakładkę „Cele”. Upewnij się, że jest sprawdzony pod kątem odpowiednich celów.
PEZ,
73
@Barry: W Cocoa widziałem wiele klas, w NSStringktórych copyzamiast definiować ich właściwości retain. W związku z tym mogą (i powinny) przechowywać inną instancję NSString*stałej, a bezpośrednie porównanie adresu pamięci nie powiedzie się. Zakładam również, że każda rozsądnie optymalna implementacja -isEqualToString:sprawdziłaby równość wskaźnika przed przejściem do drobiazgowości porównania znaków.
Ben Mosher
280

Najprostszy sposób:

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

Lepszy sposób:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

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

Jedną z zalet drugiej jest to, że zmiana wartości stałej nie powoduje przebudowy całego programu.

Andrew Grant
źródło
12
Myślałem, że nie powinieneś zmieniać wartości stałych.
ruipacheco
71
Andrew odnosi się do zmiany wartości stałej podczas kodowania, a nie podczas działania aplikacji.
Randall
7
Czy jest w tym jakaś wartość dodana extern NSString const * const MyConstant, tzn. Uczynienie go stałym wskaźnikiem do stałego obiektu, a nie tylko stałym wskaźnikiem?
Hari Karam Singh
4
Co się stanie, jeśli użyję tej deklaracji w pliku nagłówkowym, static NSString * const kNSStringConst = @ "const value"; Jaka jest różnica między niezgłaszaniem i inicjowaniem osobno w plikach .h i .m?
karim
4
@Dogweather - Miejsce, w którym tylko kompilator zna odpowiedź. IE, jeśli chcesz dołączyć do menu about, który kompilator został użyty do skompilowania kompilacji aplikacji, możesz go tam umieścić, ponieważ skompilowany kod inaczej nie miałby żadnej wiedzy. Nie mogę myśleć o wielu innych miejscach. Makra z pewnością nie należy używać w wielu miejscach. Co jeśli miałbym #define MY_CONST 5 i gdzie indziej #define MY_CONST_2 25. Rezultat jest taki, że bardzo dobrze możesz skończyć z błędem kompilatora, gdy próbuje skompilować 5_2. Nie używaj #define dla stałych. Użyj stałej dla stałych.
ArtOfWarfare
190

Jest też jedna rzecz do wspomnienia. Jeśli potrzebujesz stałej globalnej, powinieneś użyć staticsłowa kluczowego.

Przykład

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

Ze względu na staticsłowo kluczowe ta stała nie jest widoczna poza plikiem.


Drobna korekta @QuinnTaylor : zmienne statyczne są widoczne w jednostce kompilacyjnej . Zwykle jest to pojedynczy plik .m (jak w tym przykładzie), ale może cię ugryźć, jeśli zadeklarujesz go w nagłówku, który jest zawarty w innym miejscu, ponieważ po kompilacji wystąpią błędy linkera

kompozer
źródło
41
Drobna korekta: zmienne statyczne są widoczne w jednostce kompilacji . Zwykle jest to pojedynczy plik .m (jak w tym przykładzie), ale może cię ugryźć, jeśli zadeklarujesz go w nagłówku, który jest zawarty w innym miejscu, ponieważ po kompilacji wystąpią błędy linkera.
Quinn Taylor
Jeśli nie użyję słowa kluczowego static, czy kNSStringConst będzie dostępny w całym projekcie?
Danyal Aytekin
2
Ok, właśnie zaznaczone ... Xcode nie zapewnia automatycznego uzupełniania go w innych plikach, jeśli nie włączasz static, ale próbowałem umieścić tę samą nazwę w dwóch różnych miejscach i odtworzyłem błędy linkera Quinna.
Danyal Aytekin
1
statyczny w pliku nagłówkowym nie powoduje problemów z linkerem. Jednak każda jednostka kompilacji wraz z plikiem nagłówka otrzyma własną zmienną statyczną, więc otrzymasz 100 z nich, jeśli dodasz nagłówek ze 100 plików .m.
gnasher729
@kompozer W której części pliku .m umieszczasz to?
Basil Bourque
117

Przyjęta (i poprawna) odpowiedź mówi, że „możesz dołączyć ten plik [Constants.h] ... do wstępnie skompilowanego nagłówka projektu”.

Jako nowicjusz miałem trudności z zrobieniem tego bez dalszego wyjaśnienia - oto jak: W pliku YourAppNameHere-Prefix.pch (jest to domyślna nazwa prekompilowanego nagłówka w Xcode) zaimportuj plik Constants.h wewnątrz #ifdef __OBJC__bloku .

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

Zauważ też, że pliki Constants.h i Constants.m nie powinny zawierać w nich absolutnie nic poza opisanymi w zaakceptowanej odpowiedzi. (Brak interfejsu lub implementacji).

Victor Van Hee
źródło
Zrobiłem to, ale niektóre pliki zgłaszają błąd przy kompilacji „Użycie niezadeklarowanego identyfikatora„ CONSTANTSNAME ”Jeśli dołączę do pliku plik constant.h zgłaszający błąd, to działa, ale to nie jest to, co chcę zrobić. Wyczyściłem, zamknąłem xcode i kompilacja i wciąż problemy ... jakieś pomysły?
J3RM
50

Ogólnie używam sposobu opublikowanego przez Barry'ego Warka i Rahula Gupta.

Chociaż nie lubię powtarzać tych samych słów w plikach .h i .m. Zauważ, że w poniższym przykładzie linia jest prawie identyczna w obu plikach:

// file.h
extern NSString* const MyConst;

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

Dlatego lubię korzystać z niektórych maszyn preprocesora C. Pozwól mi wyjaśnić na przykładzie.

Mam plik nagłówka, który definiuje makro 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

W mojej parze .h / .m, w której chcę zdefiniować stałą, wykonuję następujące czynności:

// 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, mam wszystkie informacje o stałych tylko w pliku .h.

Krizz
źródło
Hmm, jest jednak pewne zastrzeżenie, jednak nie można użyć tej techniki, jeśli plik nagłówka zostanie zaimportowany do nagłówka prekompilowanego, ponieważ nie załaduje on pliku .h do pliku .m, ponieważ został już skompilowany. Jest jednak sposób - zobacz moją odpowiedź (ponieważ nie mogę umieścić ładnego kodu w komentarzach.
Scott Little
Nie mogę tego uruchomić. Jeśli wstawię #define SYNTHESIZE_CONSTS przed #import "myfile.h", to robi NSString * ... zarówno w .h, jak i .m (zaznaczone przy użyciu widoku asystenta i preprocesora). Zgłasza błędy redefinicji. Jeśli wstawię go po #import „myfile.h”, to wywołuje NSString * ... w obu plikach. Następnie zgłasza błędy „Niezdefiniowany symbol”.
arsenius
28

Sam mam nagłówek poświęcony deklarowaniu stałych NSStrings używanych w preferencjach, takich jak:

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

Następnie zadeklaruj je w dołączonym pliku .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";

To podejście mi dobrze służyło.

Edycja: Zauważ, że działa to najlepiej, jeśli ciągi są używane w wielu plikach. Jeśli używa go tylko jeden plik, możesz to zrobić #define kNSStringConstant @"Constant NSString"w pliku .m, który używa łańcucha.

MaddTheSane
źródło
25

Niewielka modyfikacja sugestii @Krizz, aby działała poprawnie, jeśli plik nagłówka stałych ma być zawarty w PCH, co jest raczej normalne. Ponieważ oryginał jest importowany do PCH, nie załaduje go ponownie do .mpliku, a zatem nie otrzymasz żadnych symboli, a linker jest niezadowolony.

Jednak następująca modyfikacja pozwala mu działać. To trochę skomplikowane, ale działa.

Potrzebne będą 3 pliki, .hplik ze stałymi definicjami, .hplik i .mplik, którego użyję ConstantList.h, Constants.hi Constants.modpowiednio. zawartość Constants.hjest po prostu:

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

i Constants.mplik wygląda następująco:

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

Wreszcie ConstantList.hplik zawiera rzeczywiste deklaracje i to wszystko:

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

Kilka rzeczy do zapamiętania:

  1. Musiałem ponownie zdefiniować makro w .mpliku po #undef ing go do makro ma być używany.

  2. Musiałem także użyć #includezamiast #importtego, aby działało to poprawnie i unikałem zauważenia przez kompilator wcześniej wstępnie skompilowanych wartości.

  3. Będzie to wymagało ponownej kompilacji twojego PCH (i prawdopodobnie całego projektu) za każdym razem, gdy jakiekolwiek wartości zostaną zmienione, co nie ma miejsca, jeśli zostaną one rozdzielone (i powielone) jak zwykle.

Mam nadzieję, że jest to pomocne dla kogoś.

Scott Little
źródło
1
Użycie #include naprawiło dla mnie ten ból głowy.
Ramsel
Czy ma to jakąś utratę wydajności / pamięci w porównaniu z zaakceptowaną odpowiedzią?
Gyfis
W odpowiedzi na wyniki w porównaniu do odpowiedzi zaakceptowanej nie ma żadnej. Z punktu widzenia kompilatora jest to dokładnie to samo. Skończysz z tymi samymi deklaracjami. Byłyby DOKŁADNIE takie same, gdybyś zastąpił externpowyższe przez FOUNDATION_EXPORT.
Scott Little
14
// Prefs.h
extern NSString * const RAHUL;

// Prefs.m
NSString * const RAHUL = @"rahul";
Rahul Gupta
źródło
12

Jak powiedział Abizer, możesz umieścić go w pliku PCH. Innym sposobem, który nie jest tak brudny, jest utworzenie pliku dołączania dla wszystkich kluczy, a następnie albo dołączenie go do pliku, w którym używasz kluczy, albo uwzględnienie go w PCH. Z ich własnym plikiem dołączającym, co najmniej daje jedno miejsce do wyszukiwania i definiowania wszystkich tych stałych.

Grant Limberg
źródło
11

Jeśli chcesz coś w rodzaju stałych globalnych; szybki i brudny sposób polega na umieszczeniu w pchpliku stałych deklaracji .

Abizern
źródło
7
Edycja pliku .pch zwykle nie jest najlepszym pomysłem. Musisz znaleźć miejsce do zdefiniowania zmiennej, prawie zawsze pliku .m, więc sensowniej jest zadeklarować ją w pasującym pliku .h. Przyjęta odpowiedź na utworzenie pary Constants.h / m jest dobra, jeśli potrzebujesz ich w całym projekcie. Zasadniczo umieszczam stałe tak daleko w hierarchii, jak to możliwe, w zależności od tego, gdzie zostaną użyte.
Quinn Taylor
8

Spróbuj użyć metody klasowej:

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

Czasem go używam.

groumpf
źródło
6
Metoda klasowa nie jest stała. Ma koszt w czasie wykonywania i nie zawsze może zwrócić ten sam obiekt (zrobi to, jeśli zaimplementujesz go w ten sposób, ale niekoniecznie zaimplementujesz go w ten sposób), co oznacza, że ​​musisz użyć isEqualToString:porównania, które jest dodatkowy koszt w czasie wykonywania. Kiedy chcesz stałych, twórz stałe.
Peter Hosey,
2
@Peter Hosey, chociaż twoje komentarze są słuszne, bierzemy ten hit wydajności raz na LOC lub więcej w językach „wyższego poziomu”, takich jak Ruby, nie martwiąc się o to. Nie twierdzę, że nie masz racji, ale po prostu komentuję różnice między standardami w różnych „światach”.
Dan Rosenstark,
1
Prawda o Ruby. Większość wydajności kodowanej przez ludzi jest dość niepotrzebna w przypadku typowej aplikacji.
Peter DeWeese
8

Jeśli podoba Ci się stała przestrzeni nazw, możesz wykorzystać struct, piątek Pytania i odpowiedzi 2011-08-19: Stałe przestrzeni nazw i funkcje

// 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"
};
onmyway133
źródło
1
Świetna rzecz! Ale pod ARC będziesz musiał poprzedzić wszystkie zmienne w deklaracji struktury __unsafe_unretainedkwalifikatorem, aby działał.
Cemen
7

Używam klasy singleton, aby móc wyśmiewać klasę i zmienić stałe, jeśli to konieczne do testowania. Klasa stałych wygląda następująco:

#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

I jest używany w ten sposób (zwróć uwagę na użycie skrótu dla stałych c - za [[Constants alloc] init]każdym razem oszczędza pisania ):

#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
Howard Lovatt
źródło
1

Jeśli chcesz wywołać coś takiego NSString.newLine;z celu c i chcesz, aby była stała statyczna, możesz szybko utworzyć coś takiego:

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

I masz ładną, czytelną stałą definicję, dostępną z wybranego typu, podczas gdy styl jest ograniczony do kontekstu typu.

Renetik
źródło