Costanti in Objective-C


1002

Sto sviluppando un'applicazione Cocoa e sto usando costanti NSStringcome modi per memorizzare i nomi delle chiavi per le mie preferenze.

Capisco che questa è una buona idea perché consente di cambiare facilmente le chiavi se necessario.
Inoltre, è l'intera nozione "separa i tuoi dati dalla tua logica".

Ad ogni modo, c'è un buon modo per rendere queste costanti definite una volta per l'intera applicazione?

Sono sicuro che esiste un modo semplice e intelligente, ma in questo momento le mie lezioni ridefiniscono quelle che usano.


7
OOP riguarda il raggruppamento dei dati con la logica. Quello che stai proponendo è solo una buona pratica di programmazione, cioè rendere il tuo programma facile da cambiare.
Raffi Khatchadourian,

Risposte:


1287

Dovresti creare un file header simile

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

(puoi usare externinvece che FOUNDATION_EXPORTse il tuo codice non verrà usato in ambienti misti C / C ++ o su altre piattaforme)

È possibile includere questo file in ogni file che utilizza le costanti o nell'intestazione precompilata per il progetto.

Definisci queste costanti in un file .m come

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

Constants.m deve essere aggiunto al target dell'applicazione / framework in modo che sia collegato al prodotto finale.

Il vantaggio di usare costanti di stringa invece di #define'd costanti è che puoi verificare l'uguaglianza usando il confronto puntatore ( stringInstance == MyFirstConstant) che è molto più veloce del confronto di stringhe ( [stringInstance isEqualToString:MyFirstConstant]) (e più facile da leggere, IMO).


67
Per una costante intera sarebbe: extern int const MyFirstConstant = 1;
Dan Morgan,

180
Nel complesso, un'ottima risposta, con un avvertimento evidente: NON si desidera verificare l'uguaglianza delle stringhe con l'operatore == in Objective-C, poiché verifica l'indirizzo di memoria. Usa sempre -isEqualToString: per questo. È possibile ottenere facilmente un'istanza diversa confrontando MyFirstConstant e [NSString stringWithFormat: MyFirstConstant]. Non fare ipotesi sull'istanza di una stringa che hai, anche con valori letterali. (In ogni caso, #define è una "direttiva preprocessore", e viene sostituita prima della compilazione, quindi in entrambi i casi il compilatore vede una stringa letterale alla fine.)
Quinn Taylor

74
In questo caso, è OK usare == per verificare l'uguaglianza con la costante, se è veramente usato come un simbolo di costante (cioè viene usato il simbolo MyFirstConstant invece di una stringa contenente @ "MyFirstConstant"). In questo caso è possibile utilizzare un numero intero anziché una stringa (in realtà, è quello che stai facendo - utilizzare il puntatore come numero intero) ma l'uso di una stringa costante rende il debug leggermente più semplice poiché il valore della costante ha un significato leggibile dall'uomo .
Barry Wark,

17
+1 per "Constants.m deve essere aggiunto al target dell'applicazione / framework in modo che sia collegato al prodotto finale." Mi ha salvato la sanità mentale. @amok, fai "Ottieni informazioni" su Constants.m e scegli la scheda "Target". Assicurati che sia controllato per i target pertinenti.
PEZ

73
@Barry: In Cocoa, ho visto un numero di classi che ne definiscono le NSStringproprietà copyanziché retain. Pertanto, potrebbero (e dovrebbero) contenere un'istanza diversa della tua NSString*costante e il confronto diretto degli indirizzi di memoria fallirebbe. Inoltre, presumo che qualsiasi implementazione ragionevolmente ottimale -isEqualToString:verificherebbe l'uguaglianza dei puntatori prima di entrare nel nocciolo del confronto dei caratteri.
Ben Mosher,

280

Il modo più semplice:

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

Modo migliore:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

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

Un vantaggio del secondo è che la modifica del valore di una costante non provoca una ricostruzione dell'intero programma.


12
Pensavo che non avresti dovuto cambiare il valore delle costanti.
Ruipacheco,

71
Andrew si riferisce alla modifica del valore della costante durante la codifica, non mentre l'applicazione è in esecuzione.
Randall,

7
C'è qualche valore aggiunto nel fare extern NSString const * const MyConstant, cioè renderlo un puntatore costante a un oggetto costante piuttosto che solo un puntatore costante?
Hari Karam Singh

4
Cosa succede se uso questa dichiarazione nel file di intestazione, NSString statico * const kNSStringConst = @ "const value"; Qual è la differenza tra non dichiarare e init separatamente nei file .h e .m?
Karim,

4
@Dogweather: un posto in cui solo il compilatore conosce la risposta. Ad esempio, se si desidera includere in un menu about quale compilatore è stato utilizzato per compilare una build di un'applicazione, è possibile inserirlo lì poiché il codice compilato altrimenti non avrebbe comunque saputo. Non riesco a pensare a molti altri posti. Le macro certamente non dovrebbero essere usate in molti posti. E se avessi #define MY_CONST 5 e altrove #define MY_CONST_2 25. Il risultato è che potresti finire con un errore del compilatore quando prova a compilare 5_2. Non usare #define per le costanti. Usa const per costanti.
ArtOfWarfare il

190

C'è anche una cosa da menzionare. Se hai bisogno di una costante non globale, dovresti usare la staticparola chiave.

Esempio

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

A causa della staticparola chiave, questa const non è visibile al di fuori del file.


Correzione minore di @QuinnTaylor : le variabili statiche sono visibili all'interno di un'unità di compilazione . Di solito, questo è un singolo file .m (come in questo esempio), ma può morderti se lo dichiari in un'intestazione inclusa altrove, poiché dopo la compilazione otterrai errori di linker


41
Correzione minore: le variabili statiche sono visibili all'interno di un'unità di compilazione . Di solito, questo è un singolo file .m (come in questo esempio), ma può morderti se lo dichiari in un'intestazione che è inclusa altrove, poiché dopo la compilazione otterrai errori del linker.
Quinn Taylor,

Se non utilizzo la parola chiave statica, kNSStringConst sarà disponibile in tutto il progetto?
Danyal Aytekin,

2
Ok, ho appena controllato ... Xcode non fornisce il completamento automatico per esso in altri file se si lascia statico disattivato, ma ho provato a mettere lo stesso nome in due luoghi diversi e ho riprodotto gli errori del linker di Quinn.
Danyal Aytekin,

1
statico in un file di intestazione non dà problemi al linker. Tuttavia, ogni unità di compilazione, incluso il file di intestazione, otterrà la propria variabile statica, quindi se ne ottengono 100 se si include l'intestazione da 100 file .m.
gnasher729,

@kompozer In quale parte del file .m inserisci questo?
Basil Bourque,

117

La risposta accettata (e corretta) afferma che "è possibile includere questo file [Constants.h] ... nell'intestazione precompilata per il progetto."

Come principiante, ho avuto difficoltà a farlo senza ulteriori spiegazioni - ecco come: Nel tuo file YourAppNameHere-Prefix.pch (questo è il nome predefinito per l'intestazione precompilata in Xcode), importa il tuo Costante.h all'interno del #ifdef __OBJC__blocco .

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

Si noti inoltre che i file Constants.h e Constants.m non devono contenere assolutamente nient'altro al di fuori di ciò che è descritto nella risposta accettata. (Nessuna interfaccia o implementazione).


L'ho fatto ma alcuni file generano un errore durante la compilazione "Utilizzo dell'identificatore non dichiarato 'CONSTANTSNAME' Se includo la costante.h nel file che genera l'errore, funziona, ma non è quello che voglio fare. Ho pulito, arrestato xcode e build e ancora problemi ... qualche idea?
J3RM

50

In genere sto usando il modo pubblicato da Barry Wark e Rahul Gupta.

Anche se non mi piace ripetere le stesse parole in entrambi i file .h e .m. Si noti che nell'esempio seguente la riga è quasi identica in entrambi i file:

// file.h
extern NSString* const MyConst;

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

Pertanto, ciò che mi piace fare è utilizzare alcuni macchinari preprocessore C. Lasciami spiegare con l'esempio.

Ho un file di intestazione che definisce la macro 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

Nella mia coppia .h / .m in cui desidero definire la costante faccio quanto segue:

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

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

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

et voilà, ho tutte le informazioni sulle costanti solo nel file .h.


Tuttavia, c'è un po 'di avvertimento, non è possibile utilizzare questa tecnica in questo modo se il file di intestazione viene importato nell'intestazione precompilata, perché non caricherà il file .h nel file .m perché era già compilato. C'è un modo però - vedi la mia risposta (dato che non riesco a inserire un bel codice nei commenti.
Scott Little

Non riesco a farlo funzionare. Se metto #define SYNTHESIZE_CONSTS prima di #import "myfile.h", fa NSString * ... sia in .h che in .m (Controllato usando la vista assistente e il preprocessore). Genera errori di ridefinizione. Se lo metto dopo #import "myfile.h", fa NSString esterno * ... in entrambi i file. Quindi genera errori "Simbolo indefinito".
arsenius,

28

Io stesso ho un'intestazione dedicata alla dichiarazione di stringhe NSS costanti utilizzate per le preferenze in questo modo:

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

Quindi dichiarandoli nel file .m di accompagnamento:

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

Questo approccio mi è servito bene.

Modifica: nota che funziona meglio se le stringhe vengono utilizzate in più file. Se lo utilizza solo un file, puoi farlo #define kNSStringConstant @"Constant NSString"nel file .m che utilizza la stringa.


25

Una leggera modifica del suggerimento di @Krizz, in modo che funzioni correttamente se il file di intestazione delle costanti deve essere incluso nel PCH, il che è piuttosto normale. Poiché l'originale viene importato nel PCH, non lo ricaricherà nel.m file e quindi non si ottengono simboli e il linker è infelice.

Tuttavia, la seguente modifica consente di funzionare. È un po 'contorto, ma funziona.

Avrete bisogno di 3 file, .hil file che ha le definizioni costanti, il .hfile di e la .mlima, userò ConstantList.h, Constants.he Constants.m, rispettivamente. i contenuti di Constants.hsono semplicemente:

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

e il Constants.mfile appare come:

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

Infine, il ConstantList.hfile contiene le dichiarazioni effettive e questo è tutto:

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

Un paio di cose da notare:

  1. Ho dovuto ridefinire la macro nel .mfile dopo #undef averla ing per la macro da utilizzare.

  2. Ho dovuto anche usare #includeinvece che #importper farlo funzionare correttamente ed evitare che il compilatore vedesse i valori precedentemente precompilati.

  3. Ciò richiederà una ricompilazione del tuo PCH (e probabilmente dell'intero progetto) ogni volta che i valori vengono modificati, il che non accade se vengono separati (e duplicati) normalmente.

Spero che sia utile per qualcuno.


1
L'uso di #include ha risolto questo mal di testa per me.
Ramsel,

Questo ha qualche perdita di prestazioni / memoria rispetto alla risposta accettata?
Gyfis,

In risposta alla prestazione rispetto alla risposta accettata, non c'è nessuno. È effettivamente la stessa cosa dal punto di vista del compilatore. Si finisce con le stesse dichiarazioni. Sarebbero ESATTAMENTE gli stessi se sostituissi quanto externsopra con il FOUNDATION_EXPORT.
Scott Little

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

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

12

Come ha detto Abizer, potresti inserirlo nel file PCH. Un altro modo che non è così sporco è quello di creare un file include per tutte le tue chiavi e poi includerlo nel file in cui stai usando le chiavi o includerlo nel PCH. Con loro nel loro file include, che almeno ti dà un posto dove cercare e definire tutte queste costanti.


11

Se vuoi qualcosa come le costanti globali; un modo veloce e sporco è quello di inserire le dichiarazioni costanti nel pchfile.


7
Modificare il .pch di solito non è la migliore idea. Dovrai trovare un posto per definire effettivamente la variabile, quasi sempre un file .m, quindi ha più senso dichiararlo nel corrispondente file .h. La risposta accettata alla creazione di una coppia Constants.h / m è buona se ne hai bisogno in tutto il progetto. In genere inserisco le costanti il ​​più in basso possibile nella gerarchia, in base a dove verranno utilizzate.
Quinn Taylor,

8

Prova a utilizzare un metodo di classe:

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

Lo uso a volte.


6
Un metodo di classe non è una costante. Ha un costo in fase di esecuzione e potrebbe non restituire sempre lo stesso oggetto (lo farà se lo implementi in quel modo, ma non lo hai necessariamente implementato in quel modo), il che significa che devi usare isEqualToString:per il confronto, che è un ulteriore costo in fase di esecuzione. Quando vuoi costanti, crea costanti.
Peter Hosey,

2
@Peter Hosey, mentre i tuoi commenti sono giusti, prendiamo quel successo di performance una volta per LOC o più in lingue "di alto livello" come Ruby senza preoccuparsene. Non sto dicendo che non hai ragione, ma piuttosto commentando come gli standard sono diversi nei diversi "mondi".
Dan Rosenstark,

1
Vero su Ruby. La maggior parte del codice prestazionale per le persone è del tutto superflua per l'app tipica.
Peter DeWeese,

8

Se ti piace la costante dello spazio dei nomi, puoi sfruttare la struttura, Venerdì Domande e risposte 2011-08-19: Costanti e funzioni spaziate dai nomi

// 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
Un'ottima cosa! Ma sotto ARC dovrai precedere tutte le variabili nella dichiarazione struct con __unsafe_unretainedqualificatore per farlo funzionare.
Cemen,

7

Uso una classe singleton, in modo da poter deridere la classe e modificare le costanti se necessario per il test. La classe delle costanti si presenta così:

#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

Ed è usato in questo modo (nota l'uso di una scorciatoia per le costanti c - salva [[Constants alloc] init]ogni volta la digitazione ):

#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

Se vuoi chiamare qualcosa del genere NSString.newLine;dall'obiettivo c e vuoi che sia costante statica, puoi creare qualcosa del genere in breve tempo:

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

E hai una definizione costante leggibile e disponibile da un tipo di tua scelta mentre lo stile è limitato al contesto del tipo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.