Konstanten in Ziel-C


1002

Ich entwickle eine Cocoa- Anwendung und verwende Konstante NSStrings, um Schlüsselnamen für meine Einstellungen zu speichern.

Ich verstehe, dass dies eine gute Idee ist, da es bei Bedarf ein einfaches Wechseln der Schlüssel ermöglicht.
Außerdem ist es der ganze Begriff "Trennen Sie Ihre Daten von Ihrer Logik".

Gibt es eine gute Möglichkeit, diese Konstanten für die gesamte Anwendung einmal zu definieren?

Ich bin mir sicher, dass es einen einfachen und intelligenten Weg gibt, aber im Moment definieren meine Klassen nur die, die sie verwenden.


7
Bei OOP geht es darum , Ihre Daten mit Ihrer Logik zu gruppieren . Was Sie vorschlagen, ist nur eine gute Programmierpraxis, dh Sie können Ihr Programm leicht ändern.
Raffi Khatchadourian

Antworten:


1287

Sie sollten eine Header-Datei wie erstellen

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

(Sie können externanstelle von verwenden, FOUNDATION_EXPORTwenn Ihr Code nicht in gemischten C / C ++ - Umgebungen oder auf anderen Plattformen verwendet wird.)

Sie können diese Datei in jede Datei aufnehmen, die die Konstanten verwendet, oder in den vorkompilierten Header für das Projekt.

Sie definieren diese Konstanten in einer .m-Datei wie

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

Constants.m sollte dem Ziel Ihrer Anwendung / Ihres Frameworks hinzugefügt werden, damit es mit dem Endprodukt verknüpft wird.

Der Vorteil der Verwendung von Zeichenfolgenkonstanten anstelle von #define'd-Konstanten besteht darin, dass Sie die Gleichheit mithilfe des Zeigervergleichs ( stringInstance == MyFirstConstant) testen können, der viel schneller als der Zeichenfolgenvergleich ( [stringInstance isEqualToString:MyFirstConstant]) ist (und einfacher zu lesen ist, IMO).


67
Für eine Ganzzahlkonstante wäre es: extern int const MyFirstConstant = 1;
Dan Morgan

180
Insgesamt eine großartige Antwort mit einer eklatanten Einschränkung: Sie möchten NICHT mit dem Operator == in Objective-C auf String-Gleichheit testen, da er die Speicheradresse testet. Verwenden Sie dazu immer -isEqualToString :. Sie können leicht eine andere Instanz erhalten, indem Sie MyFirstConstant und [NSString stringWithFormat: MyFirstConstant] vergleichen. Machen Sie keine Annahmen darüber, welche Instanz einer Zeichenfolge Sie haben, auch nicht mit Literalen. (In jedem Fall ist #define eine "Präprozessor-Direktive" und wird vor dem Kompilieren ersetzt, sodass der Compiler am Ende in beiden Fällen ein String-Literal sieht.)
Quinn Taylor

74
In diesem Fall ist es in Ordnung, == zu verwenden, um die Gleichheit mit der Konstante zu testen, wenn sie wirklich als Konstantensymbol verwendet wird (dh das Symbol MyFirstConstant anstelle einer Zeichenfolge mit @ "MyFirstConstant" wird verwendet). In diesem Fall könnte eine Ganzzahl anstelle einer Zeichenfolge verwendet werden (genau das tun Sie - indem Sie den Zeiger als Ganzzahl verwenden). Die Verwendung einer konstanten Zeichenfolge erleichtert jedoch das Debuggen etwas, da der Wert der Konstante eine vom Menschen lesbare Bedeutung hat .
Barry Wark

17
+1 für "Constants.m sollte zum Ziel Ihrer Anwendung / Ihres Frameworks hinzugefügt werden, damit es mit dem Endprodukt verknüpft wird." Hat meine geistige Gesundheit gerettet. @amok, mache "Get info" auf Constants.m und wähle die Registerkarte "Targets". Stellen Sie sicher, dass die relevanten Ziele überprüft wurden.
PEZ

73
@Barry: In Cocoa habe ich eine Reihe von Klassen gesehen, die ihre NSStringEigenschaften mit copyanstelle von definieren retain. Als solche könnten (und sollten) sie eine andere Instanz Ihrer NSString*Konstanten halten, und ein direkter Vergleich der Speicheradressen würde fehlschlagen. Außerdem würde ich davon ausgehen, dass jede einigermaßen optimale Implementierung von auf -isEqualToString:Zeigergleichheit prüfen würde, bevor auf den Kern des Zeichenvergleichs eingegangen wird.
Ben Mosher

280

Einfachster Weg:

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

Besserer Weg:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

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

Ein Vorteil des zweiten ist, dass das Ändern des Werts einer Konstante nicht zu einer Neuerstellung Ihres gesamten Programms führt.


12
Ich dachte, Sie sollten den Wert von Konstanten nicht ändern.
Ruipacheco

71
Andrew bezieht sich auf das Ändern des Werts der Konstante während des Codierens, nicht während die Anwendung ausgeführt wird.
Randall

7
Gibt es einen Mehrwert extern NSString const * const MyConstant, dh es ist ein konstanter Zeiger auf ein konstantes Objekt und nicht nur ein konstanter Zeiger?
Hari Karam Singh

4
Was passiert, wenn ich diese Deklaration in der Header-Datei verwende? Static NSString * const kNSStringConst = @ "const value"; Was ist der Unterschied zwischen nicht getrennt deklarieren und init in .h- und .m-Dateien?
Karim

4
@ Dogweather - Irgendwo, wo nur der Compiler die Antwort kennt. IE, wenn Sie in ein About-Menü aufnehmen möchten, welcher Compiler zum Kompilieren eines Builds einer Anwendung verwendet wurde, können Sie ihn dort platzieren, da der kompilierte Code sonst sowieso nichts davon wissen würde. Ich kann mir nicht viele andere Orte vorstellen. Makros sollten sicherlich nicht an vielen Orten verwendet werden. Was wäre, wenn ich #define MY_CONST 5 und anderswo #define MY_CONST_2 25 hätte? Das Ergebnis ist, dass Sie möglicherweise einen Compilerfehler erhalten, wenn Sie versuchen, 5_2 zu kompilieren. Verwenden Sie #define nicht für Konstanten. Verwenden Sie const für Konstanten.
ArtOfWarfare

190

Es gibt auch eine Sache zu erwähnen. Wenn Sie eine nicht globale Konstante benötigen, sollten Sie das staticSchlüsselwort verwenden.

Beispiel

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

Aufgrund des staticSchlüsselworts ist diese Konstante außerhalb der Datei nicht sichtbar.


Kleinere Korrektur durch @QuinnTaylor : Statische Variablen sind in einer Kompilierungseinheit sichtbar . Normalerweise ist dies eine einzelne .m-Datei (wie in diesem Beispiel), aber sie kann Sie beißen, wenn Sie sie in einem Header deklarieren, der an anderer Stelle enthalten ist, da nach dem Kompilieren Linkerfehler auftreten


41
Kleinere Korrektur: Statische Variablen sind in einer Kompilierungseinheit sichtbar . Normalerweise ist dies eine einzelne .m-Datei (wie in diesem Beispiel), aber sie kann Sie beißen, wenn Sie sie in einem Header deklarieren, der an anderer Stelle enthalten ist, da nach dem Kompilieren Linkerfehler auftreten.
Quinn Taylor

Wenn ich das statische Schlüsselwort nicht verwende, ist kNSStringConst dann im gesamten Projekt verfügbar?
Danyal Aytekin

2
Ok, nur überprüft ... Xcode bietet keine automatische Vervollständigung in anderen Dateien, wenn Sie die statische Aufladung deaktiviert haben, aber ich habe versucht, denselben Namen an zwei verschiedenen Stellen zu platzieren, und Quinns Linkerfehler reproduziert.
Danyal Aytekin

1
statisch in einer Header-Datei gibt keine Linker-Probleme. Jede Kompilierungseinheit, die die Header-Datei enthält, erhält jedoch eine eigene statische Variable, sodass Sie 100 davon erhalten, wenn Sie den Header aus 100-m-Dateien einschließen.
Gnasher729

@kompozer In welchem ​​Teil der .m-Datei platzieren Sie dies?
Basil Bourque

117

Die akzeptierte (und korrekte) Antwort lautet: "Sie können diese [Constants.h] -Datei ... in den vorkompilierten Header für das Projekt aufnehmen."

Als Anfänger hatte ich Schwierigkeiten, dies ohne weitere Erklärung zu tun - so geht's: Importieren Sie in Ihrer YourAppNameHere-Prefix.pch-Datei (dies ist der Standardname für den vorkompilierten Header in Xcode) Ihre Constants.h in den #ifdef __OBJC__Block .

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

Beachten Sie auch, dass die Dateien Constants.h und Constants.m absolut nichts anderes enthalten sollten als das, was in der akzeptierten Antwort beschrieben ist. (Keine Schnittstelle oder Implementierung).


Ich habe dies getan, aber einige Dateien werfen beim Kompilieren einen Fehler aus. "Verwendung des nicht deklarierten Bezeichners 'CONSTANTSNAME' Wenn ich die Konstante.h in die Datei einbinde, die den Fehler auslöst, funktioniert dies, aber das ist nicht das, was ich tun möchte. Ich habe bereinigt, heruntergefahren Xcode und Build und immer noch Probleme ... irgendwelche Ideen?
J3RM

50

Ich benutze im Allgemeinen den Weg von Barry Wark und Rahul Gupta.

Obwohl ich es nicht mag, die gleichen Wörter sowohl in der .h- als auch in der .m-Datei zu wiederholen. Beachten Sie, dass im folgenden Beispiel die Zeile in beiden Dateien nahezu identisch ist:

// file.h
extern NSString* const MyConst;

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

Daher verwende ich gerne einige C-Präprozessor-Maschinen. Lassen Sie mich anhand des Beispiels erklären.

Ich habe eine Header-Datei, die das Makro definiert 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

In meinem .h / .m-Paar, in dem ich die Konstante definieren möchte, mache ich Folgendes:

// 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, ich habe alle Informationen über die Konstanten nur in der .h-Datei.


Hmm, es gibt eine kleine Einschränkung, aber Sie können diese Technik nicht so verwenden, wenn die Header-Datei in den vorkompilierten Header importiert wird, da die .h-Datei nicht in die .m-Datei geladen wird, da sie bereits kompiliert wurde. Es gibt jedoch einen Weg - siehe meine Antwort (da ich keinen netten Code in die Kommentare einfügen kann.
Scott Little

Ich kann das nicht zum Laufen bringen. Wenn ich #define SYNTHESIZE_CONSTS vor #import "myfile.h" setze, wird NSString * ... sowohl in .h als auch in .m ausgeführt (Wird mithilfe der Assistentenansicht und des Präprozessors überprüft). Es werden Neudefinitionsfehler ausgelöst. Wenn ich es nach #import "myfile.h" setze, wird in beiden Dateien extern NSString * ... ausgeführt. Dann werden "Undefinierte Symbole" -Fehler ausgegeben.
Arsenius

28

Ich selbst habe einen Header, der dazu dient, konstante NSStrings zu deklarieren, die für folgende Einstellungen verwendet werden:

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

Dann deklarieren Sie sie in der zugehörigen .m-Datei:

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

Dieser Ansatz hat mir gute Dienste geleistet.

Bearbeiten: Beachten Sie, dass dies am besten funktioniert, wenn die Zeichenfolgen in mehreren Dateien verwendet werden. Wenn nur eine Datei es verwendet, können Sie dies einfach #define kNSStringConstant @"Constant NSString"in der .m-Datei tun , die die Zeichenfolge verwendet.


25

Eine geringfügige Änderung des Vorschlags von @Krizz, damit er ordnungsgemäß funktioniert, wenn die Konstanten-Header-Datei in den PCH aufgenommen werden soll, was eher normal ist. Da das Original in den PCH importiert wird, wird es nicht erneut in den PCH geladen.m Datei sodass Sie keine Symbole erhalten und der Linker unglücklich ist.

Mit der folgenden Änderung kann es jedoch funktionieren. Es ist ein bisschen verworren, aber es funktioniert.

Sie benötigen drei Dateien, .hDatei , die die Konstanten - Definitionen, die hat .hDatei und die .mDatei, werde ich verwenden ConstantList.h, Constants.hund Constants.m, respectively. Der Inhalt von Constants.hist einfach:

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

und die Constants.mDatei sieht aus wie:

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

Schließlich enthält die ConstantList.hDatei die tatsächlichen Deklarationen und das ist alles:

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

Ein paar Dinge zu beachten:

  1. Ich musste das Makro in der .mDatei neu definieren, nachdem ich #undefes für das zu verwendende Makro verwendet hatte.

  2. Ich musste #includestattdessen auch verwenden, #importdamit dies richtig funktioniert und der Compiler die zuvor vorkompilierten Werte nicht sieht.

  3. Dies erfordert eine Neukompilierung Ihres PCH (und wahrscheinlich des gesamten Projekts), wenn Werte geändert werden. Dies ist nicht der Fall, wenn sie wie gewohnt getrennt (und dupliziert) werden.

Hoffe das ist hilfreich für jemanden.


1
Mit #include wurden diese Kopfschmerzen für mich behoben.
Ramsel

Hat dies einen Leistungs- / Speicherverlust im Vergleich zur akzeptierten Antwort?
Gyfis

Als Antwort auf die Leistung im Vergleich zur akzeptierten Antwort gibt es keine. Aus Sicht des Compilers ist es genau dasselbe. Sie erhalten die gleichen Erklärungen. Sie wären genau gleich, wenn Sie die externoben genannten durch die ersetzen würden FOUNDATION_EXPORT.
Scott Little

14
// Prefs.h
extern NSString * const RAHUL;

// Prefs.m
NSString * const RAHUL = @"rahul";

12

Wie Abizer sagte, könnten Sie es in die PCH-Datei einfügen. Eine andere Möglichkeit, die nicht so schmutzig ist, besteht darin, eine Include-Datei für alle Ihre Schlüssel zu erstellen und diese dann entweder in die Datei aufzunehmen, in der Sie die Schlüssel verwenden, oder sie in den PCH aufzunehmen. Wenn Sie sie in ihrer eigenen Include-Datei haben, haben Sie mindestens einen Ort, an dem Sie alle diese Konstanten suchen und definieren können.


11

Wenn Sie so etwas wie globale Konstanten wollen; Ein schneller und schmutziger Weg besteht darin, die konstanten Deklarationen in die pchDatei einzufügen.


7
Das Bearbeiten der .pch-Datei ist normalerweise nicht die beste Idee. Sie müssen einen Ort finden, an dem die Variable tatsächlich definiert werden kann, fast immer eine .m-Datei. Daher ist es sinnvoller , sie in der passenden .h-Datei zu deklarieren . Die akzeptierte Antwort zum Erstellen eines Constants.h / m-Paares ist gut, wenn Sie sie für das gesamte Projekt benötigen. Im Allgemeinen setze ich Konstanten so weit wie möglich in die Hierarchie ein, je nachdem, wo sie verwendet werden.
Quinn Taylor

8

Versuchen Sie es mit einer Klassenmethode:

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

Ich benutze es manchmal.


6
Eine Klassenmethode ist keine Konstante. Es hat zur Laufzeit Kosten und gibt möglicherweise nicht immer dasselbe Objekt zurück (wenn Sie es auf diese Weise implementieren, aber nicht unbedingt auf diese Weise implementiert haben), was bedeutet, dass Sie es isEqualToString:für den Vergleich verwenden müssen weitere Kosten zur Laufzeit. Wenn Sie Konstanten möchten, erstellen Sie Konstanten.
Peter Hosey

2
@Peter Hosey, während Ihre Kommentare richtig sind, nehmen wir diesen Leistungseinbruch einmal pro LOC oder mehr in "höheren" Sprachen wie Ruby, ohne sich darüber Gedanken zu machen. Ich sage nicht, dass Sie nicht Recht haben, sondern nur zu kommentieren, wie unterschiedlich Standards in verschiedenen "Welten" sind.
Dan Rosenstark

1
Richtig bei Ruby. Der größte Teil der Leistung, für die Menschen Code schreiben, ist für die typische App nicht erforderlich.
Peter DeWeese

8

Wenn Sie eine Namespace-Konstante mögen, können Sie struct nutzen. Freitag, Fragen und Antworten 2011-08-19: Namespaced-Konstanten und -Funktionen

// 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
Eine tolle Sache! Unter ARC müssen Sie jedoch allen Variablen in der Strukturdeklaration das __unsafe_unretainedQualifikationsmerkmal voranstellen , damit es funktioniert.
Cemen

7

Ich verwende eine Singleton-Klasse, damit ich die Klasse verspotten und die Konstanten bei Bedarf zum Testen ändern kann. Die Konstantenklasse sieht folgendermaßen aus:

#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

Und es wird so verwendet (beachten Sie die Verwendung einer Kurzform für die Konstanten c - es spart [[Constants alloc] init]jedes Mal die Eingabe ):

#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

Wenn Sie so etwas NSString.newLine;von Ziel c aus aufrufen möchten und möchten, dass es statisch konstant ist, können Sie so etwas in Kürze erstellen:

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

Und Sie haben eine gut lesbare Konstantendefinition, die innerhalb eines Typs Ihrer Wahl verfügbar ist, während der Stil an den Kontext des Typs gebunden ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.