Dove memorizzare le costanti globali in un'applicazione iOS?


111

La maggior parte dei modelli nella mia app iOS interroga un server web. Vorrei avere un file di configurazione che memorizzi l'URL di base del server. Assomiglierà a questo:

// production
// static NSString* const baseUrl = "http://website.com/"

// testing
static NSString* const baseUrl = "http://192.168.0.123/"

Commentando una riga o l'altra, posso cambiare istantaneamente il server a cui puntano i miei modelli. La mia domanda è: qual è la procedura migliore per memorizzare le costanti globali in iOS? Nella programmazione Android, abbiamo questo file di risorse di stringhe integrato . In qualsiasi attività (l'equivalente di un UIViewController ), possiamo recuperare quelle costanti di stringa con:

String string = this.getString(R.string.someConstant);

Mi chiedevo se iOS SDK ha un posto analogo per memorizzare le costanti. In caso negativo, qual è la migliore pratica in Objective-C per farlo?

Risposte:


145

Potresti anche fare un file

#define kBaseURL @"http://192.168.0.123/"

in un file di intestazione "costanti", ad esempio constants.h. Quindi fa

#include "constants.h"

all'inizio di ogni file in cui è necessaria questa costante.

In questo modo, puoi passare da un server all'altro a seconda dei flag del compilatore, come in:

#ifdef DEBUG
    #define kBaseURL @"http://192.168.0.123/"
#else
    #define kBaseURL @"http://myproductionserver.com/"
#endif

Uso l' "constants.h"approccio, dichiarando le staticvariabili basate su #ifdef VIEW_CONSTANTS ... #endif. Quindi ho un file di costanti a livello di applicazione, ma ciascuno degli altri miei file di codice #defines diversi set di costanti da includere prima #includedel file delle costanti (interrompe tutti gli avvisi del compilatore "definiti ma non utilizzati").

2
Ci sono due problemi che ho riscontrato con questa soluzione. In primo luogo, quando l'ho usato #decalare, ho ricevuto un errore di compilazione che diceva " dichiarazione di direttiva di pre-elaborazione non valida ". Quindi l'ho cambiato in #define. L'altro problema è usare la costante. Volevo creare un'altra costante con static NSString* const fullUrl = [NSString stringWithFormat:@"%@%@", kbaseUrl, @"script.php"], ma a quanto pare è illegale creare const con un'espressione. Ottengo l'errore "l' elemento inizializzatore non è costante ".
JoJo

1
@Cyrille Android è davvero interessante da praticare, ci sono alcune possibilità che non potresti immaginare su iOS! Grazie comunque per la risposta
klefevre

8
Preferisci const su #define dove possibile: ottieni un migliore controllo in fase di compilazione e il debug funziona meglio.
occulus

2
@AnsonYao di solito quando mi succede mi sono dimenticato di rimuovere un punto e virgola dalla #define, come ad esempio #define kBaseURL @"http://192.168.0.123/";
Gyfis

168

Bene, vuoi la dichiarazione locale alle interfacce a cui si riferisce: il file delle costanti a livello di app non è una buona cosa.

Inoltre, è preferibile dichiarare semplicemente un extern NSString* constsimbolo, piuttosto che utilizzare un #define:


SomeFile.h

extern NSString* const MONAppsBaseUrl;

SomeFile.m

#import "SomeFile.h"

#ifdef DEBUG
NSString* const MONAppsBaseUrl = @"http://192.168.0.123/";
#else
NSString* const MONAppsBaseUrl = @"http://website.com/";
#endif

A parte l'omissione della dichiarazione Extern compatibile con C ++, questo è ciò che generalmente vedrai usato nei framework Obj-C di Apple.

Se la costante deve essere visibile a un solo file o funzione, allora static NSString* const baseUrlnel tuo *.mva bene.


26
Non sono sicuro del motivo per cui la risposta accettata ha 40 voti per sostenere #define - const è davvero migliore.
occulus

1
Sicuramente const NSString è meglio di #define, questa dovrebbe essere la risposta accettata. #define crea una nuova stringa ogni volta che viene utilizzato il valore definito.
jbat100

1
@ jbat100 Non credo che crei una nuova stringa. Penso che il compilatore rilevi se il tuo codice crea la stessa stringa statica 300.000 volte e la creerà solo una volta. @"foo"non è lo stesso di [[NSString alloc] initWithCString:"foo"].
Abhi Beckert

@ AbhiBeckert penso che il punto che jbat stava cercando di fare è che è possibile finire con duplicati della tua costante quando #defineviene utilizzata (cioè l'uguaglianza del puntatore potrebbe fallire) - non che un'espressione letterale NSString produca una temporanea ogni volta che viene eseguita.
justin

1
Sono d'accordo #define è una cattiva idea, volevo solo correggere l'errore che ha fatto che creerà più oggetti. Inoltre, non è possibile fare affidamento sull'uguaglianza del puntatore nemmeno per le costanti. Potrebbe essere caricato da NSUserDefaults o qualcosa del genere. Usa sempre isEqual :.
Abhi Beckert

39

Il modo in cui definisco le costanti globali:


AppConstants.h

extern NSString* const kAppBaseURL;

AppConstants.m

#import "AppConstants.h"

#ifdef DEBUG
NSString* const kAppBaseURL = @"http://192.168.0.123/";
#else
NSString* const kAppBaseURL = @"http://website.com/";
#endif

Quindi nel tuo file {$ APP} -Prefix.pch:

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

In caso di problemi, assicurarsi prima di avere l'opzione Precompile Prefix Header impostata su NO.


5

Puoi anche concatenare costanti stringa in questo modo:

  #define kBaseURL @"http://myServer.com"
  #define kFullURL kBaseURL @"/api/request"

4

Penso che un altro modo per farlo sia molto più semplice e lo includerai semplicemente nei file in cui devi includerli, non in TUTTI i file, come con il file prefisso .pch:

#ifndef Constants_h
#define Constants_h

//Some constants
static int const ZERO = 0;
static int const ONE = 1;
static int const TWO = 2;

#endif /* Constants_h */

Dopodiché includi questo file di intestazione nel file di intestazione che desideri. Lo includi nel file di intestazione per la classe specifica in cui desideri includerlo:

#include "Constants.h"

Nel mio test, le costanti statiche non sono utilizzabili nel debugger (lldb di Xcode). "error: use of undeclared identifier .."
jk7

3
  1. Definisco la costante globale nel file YOURPROJECT-Prefix.pch.
  2. #define BASEURl @"http://myWebService.appspot.com/xyz/xx"
  3. quindi ovunque nel progetto per utilizzare BASEURL:

    NSString *LOGIN_URL= [BASEURl stringByAppendingString:@"/users/login"];

Aggiornato: in Xcode 6 non troverai il file .pch predefinito creato nel tuo progetto. Quindi, usa il file PCH in Xcode 6 per inserire il file .pch nel tuo progetto.

Aggiornamenti: per SWIFT

  1. Crea un nuovo file Swift [vuoto senza classe] dì [AppGlobalMemebers]
  2. & Immediatamente dichiara / definisce membro

    Esempio:

    var STATUS_BAR_GREEN : UIColor  = UIColor(red: 106/255.0, green: 161/255.0, blue: 7/255.0, alpha: 1)  //
    1. Se si desidera definire il membro globale dell'app in qualsiasi file di classe, ad esempio Appdelegate o Singleton class o qualsiasi altra, dichiara il membro specificato sopra la definizione della classe

2

Le dichiarazioni globali sono interessanti ma, per me, ciò che ha cambiato profondamente il mio modo di scrivere codice è stato avere istanze globali di classi. Mi ci sono voluti un paio di giorni per capire davvero come lavorarci, quindi l'ho riassunto rapidamente qui

Uso istanze globali di classi (1 o 2 per progetto, se necessario), per raggruppare l'accesso ai dati di base o alcune logiche commerciali.

Ad esempio, se vuoi avere un oggetto centrale che gestisca tutti i tavoli del ristorante che crei, fai l'oggetto all'avvio e basta. Questo oggetto può gestire gli accessi al database OPPURE gestirlo in memoria se non è necessario salvarlo. È centralizzato, mostri solo interfacce utili ...!

È un grande aiuto, orientato agli oggetti e un buon modo per ottenere tutte le tue cose nello stesso posto

Poche righe di codice:

@interface RestaurantManager : NSObject
    +(id) sharedInstance;
    -(void)registerForTable:(NSNumber *)tableId;
@end 

e implementazione dell'oggetto:

@implementation RestaurantManager

+ (id) sharedInstance {
    static dispatch_once_t onceQueue;

    dispatch_once(&onceQueue, ^{
        sharedInstance = [[self alloc] init];
        NSLog(@"*** Shared instance initialisation ***");
    });
    return sharedInstance;
}

-(void)registerForTable:(NSNumber *)tableId {
}
@end

per usarlo è davvero semplice:

[[RestaurantManager sharedInstance] registerForTable: [NsNumber numberWithInt: 10]]


3
Il nome tecnico di questo modello di progettazione è Singleton. en.wikipedia.org/wiki/Singleton_pattern
Basil Bourque

Mantenere dati statici (classi non statiche) in sharedmanager non è una buona idea.
Onder OZCAN

1

La risposta accettata ha 2 punti deboli. In primo luogo, come altri hanno sottolineato l'utilizzo di #definecui è più difficile eseguire il debug, utilizzare invece la extern NSString* const kBaseUrlstruttura. Secondo, definisce un singolo file per le costanti. IMO, questo è sbagliato perché la maggior parte delle classi non ha bisogno dell'accesso a quelle costanti o per accedere a tutte e il file può diventare gonfio se tutte le costanti vengono dichiarate lì. Una soluzione migliore sarebbe modulare le costanti a 3 diversi livelli:

  1. Livello di sistema: SystemConstants.ho AppConstants.h che descrive le costanti a livello globale, a cui può accedere qualsiasi classe del sistema. Dichiara qui solo quelle costanti a cui è necessario accedere da classi diverse che non sono correlate.

  2. Livello ModuleNameConstants.hmodulo / sottosistema :, descrive un insieme di costanti tipiche di un insieme di classi correlate, all'interno di un modulo / sottosistema.

  3. Livello di classe: le costanti risiedono nella classe e vengono utilizzate solo da essa.

Solo 1,2 sono correlati alla domanda.


0

Un approccio che ho usato prima è creare un file Settings.pliste caricarlo NSUserDefaultsall'avvio utilizzando registerDefaults:. È quindi possibile accedere ai suoi contenuti con quanto segue:

// Assuming you've defined some constant kMySettingKey.
[[NSUserDefaults standardUserDefaults] objectForKey:kMySettingKey];

Anche se non ho fatto alcuno sviluppo per Android, sembra che sia analogo al file di risorse delle stringhe che hai descritto. L'unico svantaggio è che non puoi usare il preprocessore per scambiare le impostazioni (ad esempio in DEBUGmodalità). Suppongo che potresti caricare in un file diverso, però.

NSUserDefaults documentazione.


9
Non è un po 'eccessivo quando tutto ciò che vuoi è una costante? E inoltre, perché metterlo in un file potenzialmente modificabile? (Soprattutto quando è qualcosa di così critico come l'IP del tuo server principale, senza il quale la tua app non funziona).
Cyrille

Sento che questo approccio ha diversi vantaggi, l'essere più significativo che le impostazioni vengono restituiti nel formato corretto ( NSString, NSNumber, ecc). Certo, potresti avvolgere i tuoi messaggi #defineper fare la stessa cosa, ma poi non sono così facili da modificare. Anche l' plistinterfaccia di editing è carina. :) Anche se sono d'accordo sul fatto che non dovresti mettere cose super segrete come chiavi di crittografia lì dentro, non sono troppo preoccupato per gli utenti che stanno frugando in posti in cui non dovrebbero essere: se rompono l'app, è colpa loro .
Chris Doble

1
Certo, sono d'accordo con i tuoi argomenti. Come dici tu, inserisco le mie #defines per restituire il tipo corretto, ma sono abituato a modificare tali file di costanti, poiché ho sempre imparato a mettere costanti globali come questo in un file di costanti separato, dai giorni in cui ho imparato il Pascal su un vecchio 286 :) E per quanto riguarda l'utente che fruga ovunque, sono d'accordo anch'io, è colpa loro. È solo una questione di gusti, davvero.
Cyrille

@ Chris Doble: No, i file di risorse in Android non sono simili a NSUserDefaults. SharedPreferences e Preferences sono l'equivalente Android di NSUserDefaults (sebbene più potenti di NSUserDefaults). Le risorse in Android hanno lo scopo di separare la logica dal contenuto, come per la localizzazione e per molti altri usi.
MRD

0

Per un numero puoi usarlo come

#define MAX_HEIGHT 12.5

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.