Où stocker les constantes globales dans une application iOS?


111

La plupart des modèles de mon application iOS interrogent un serveur Web. Je souhaite avoir un fichier de configuration stockant l'URL de base du serveur. Cela ressemblera à quelque chose comme ceci:

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

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

En commentant une ligne ou une autre, je peux instantanément changer le serveur vers lequel mes modèles pointent. Ma question est la suivante: quelle est la meilleure pratique pour stocker des constantes globales dans iOS? Dans la programmation Android, nous avons ce fichier de ressources de chaînes intégré . Dans n'importe quelle activité (l'équivalent d'un UIViewController ), nous pouvons récupérer ces constantes de chaîne avec:

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

Je me demandais si le SDK iOS avait un emplacement analogue pour stocker les constantes. Si non, quelle est la meilleure pratique en Objective-C pour le faire?

Réponses:


145

Vous pouvez également faire un

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

dans un fichier d'en-tête "constantes", par exemple constants.h. Alors fais

#include "constants.h"

en haut de chaque fichier où vous avez besoin de cette constante.

De cette façon, vous pouvez basculer entre les serveurs en fonction des indicateurs du compilateur, comme dans:

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

J'utilise l' "constants.h"approche, déclarant des staticvariables basées sur #ifdef VIEW_CONSTANTS ... #endif. J'ai donc un fichier de constantes à l'échelle de l'application, mais chacun de mes autres fichiers de code a #definedifférents ensembles de constantes à inclure avant #include-ing le fichier de constantes (arrête tous les avertissements du compilateur "définis mais non utilisés").

2
Il y a deux problèmes que j'ai rencontrés avec cette solution. Tout d'abord, lorsque j'ai utilisé #decalare, j'ai eu une erreur de compilation disant " directive de prétraitement invalide déclarer ". Alors je l'ai changé à la #defineplace. L'autre problème est l'utilisation de la constante. Je voulais créer une autre constante avec static NSString* const fullUrl = [NSString stringWithFormat:@"%@%@", kbaseUrl, @"script.php"], mais apparemment, il est illégal de créer des consts avec une expression. J'obtiens l'erreur "l' élément d'initialisation n'est pas constant ".
JoJo

1
@Cyrille Android est vraiment intéressant à pratiquer, il y a des possibilités que vous ne pouviez imaginer sur iOS! Merci quand même pour la réponse
klefevre

8
Préférez const à #define lorsque cela est possible - vous obtenez une meilleure vérification à la compilation et le débogage fonctionne mieux.
occulus

2
@AnsonYao généralement quand cela m'arrive, j'ai oublié de supprimer un point-virgule de la #define, comme #define kBaseURL @"http://192.168.0.123/";
Gyfis

168

Eh bien, vous voulez que la déclaration soit locale aux interfaces auxquelles elle se rapporte - le fichier de constantes à l'échelle de l'application n'est pas une bonne chose.

De plus, il est préférable de simplement déclarer un extern NSString* constsymbole, plutôt que d'utiliser 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

Outre l'omission de la déclaration Extern compatible C ++, c'est ce que vous verrez généralement utilisé dans les frameworks Obj-C d'Apple.

Si la constante doit être visible pour un seul fichier ou une seule fonction, alors static NSString* const baseUrldans your *.mis good.


26
Je ne sais pas pourquoi la réponse acceptée a 40 voix pour préconiser #define - const est en effet mieux.
occulus

1
Certainement const NSString est meilleur que #define, cela devrait être la réponse acceptée. #define crée une nouvelle chaîne à chaque fois que la valeur définie est utilisée.
jbat100

1
@ jbat100 Je ne pense pas que cela crée une nouvelle chaîne. Je pense que le compilateur détecte si votre code crée la même chaîne statique 300 000 fois et ne la créera qu'une seule fois. @"foo"n'est pas la même chose que [[NSString alloc] initWithCString:"foo"].
Abhi Beckert

@AbhiBeckert Je pense que le point que jbat essayait de faire est qu'il est possible de se retrouver avec des doublons de votre constante lorsqu'elle #defineest utilisée (c'est-à-dire que l'égalité de pointeur peut échouer) - non pas qu'une expression littérale NSString produise un temporaire chaque fois qu'elle est exécutée.
justin

1
Je suis d'accord que #define est une mauvaise idée, je voulais juste corriger l'erreur qu'il a faite qu'il créera plusieurs objets. En outre, l'égalité des pointeurs ne peut pas être invoquée même pour les constantes. Il peut être chargé à partir de NSUserDefaults ou autre. Utilisez toujours isEqual :.
Abhi Beckert

39

La façon dont je définis les constantes globales:


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

Puis dans votre fichier {$ APP} -Prefix.pch:

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

Si vous rencontrez des problèmes, assurez-vous d'abord que l'option En-tête de préfixe de précompilation est définie sur NON.


5

Vous pouvez également concaténer des constantes de chaîne comme ceci:

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

4

Je pense qu'une autre façon de faire est beaucoup plus simple et vous allez simplement l'inclure dans les fichiers dans lesquels vous en avez besoin, pas TOUS les fichiers, comme avec le fichier de préfixe .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 */

Après cela, vous incluez ce fichier d'en-tête dans le fichier d'en-tête souhaité. Vous l'incluez dans le fichier d'en-tête de la classe spécifique dans laquelle vous souhaitez qu'il soit inclus:

#include "Constants.h"

Dans mes tests, les constantes const statiques ne sont pas utilisables dans le débogueur (lldb de Xcode). "error: use of undeclared identifier .."
jk7

3
  1. Je définis la constante globale dans le fichier YOURPROJECT-Prefix.pch.
  2. #define BASEURl @"http://myWebService.appspot.com/xyz/xx"
  3. puis n'importe où dans le projet pour utiliser BASEURL:

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

Mise à jour: dans Xcode 6, vous ne trouverez pas de fichier .pch par défaut créé dans votre projet. Veuillez donc utiliser le fichier PCH dans Xcode 6 pour insérer un fichier .pch dans votre projet.

Mises à jour: pour SWIFT

  1. Créer un nouveau fichier Swift [vide sans classe] dire [AppGlobalMemebers]
  2. & Déclarer / définir immédiatement un membre

    Exemple:

    var STATUS_BAR_GREEN : UIColor  = UIColor(red: 106/255.0, green: 161/255.0, blue: 7/255.0, alpha: 1)  //
    1. Si vous souhaitez définir le membre global de l'application dans un fichier de classe, par exemple une classe Appdelegate ou Singleton ou tout autre, déclarez le membre donné au-dessus de la définition de classe

2

Les déclarations globales sont intéressantes mais, pour moi, ce qui a profondément changé ma façon de coder était d'avoir des instances globales de classes. Il m'a fallu quelques jours pour vraiment comprendre comment travailler avec, alors je l'ai rapidement résumé ici

J'utilise des instances globales de classes (1 ou 2 par projet, si nécessaire), pour regrouper l'accès aux données de base, ou certaines logiques de métiers.

Par exemple, si vous souhaitez avoir un objet central gérant toutes les tables de restaurant, vous créez votre objet au démarrage et c'est tout. Cet objet peut gérer les accès à la base de données OU le gérer en mémoire si vous n'avez pas besoin de l'enregistrer. C'est centralisé, vous ne montrez que les interfaces utiles ...!

C'est une aide précieuse, orientée objet et un bon moyen d'obtenir tout ce que vous avez au même endroit

Quelques lignes de code:

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

et implémentation d'objet:

@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

pour l'utiliser c'est vraiment simple:

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


3
Le nom technique de ce modèle de conception est Singleton. en.wikipedia.org/wiki/Singleton_pattern
Basil Bourque

Garder des données statiques (pas des classes statiques) dans sharedmanager n'est pas une bonne idée.
Onder OZCAN

1

La réponse acceptée a 2 faiblesses. Tout d'abord, comme d'autres l'ont souligné, son utilisation #defineest plus difficile à déboguer, utilisez plutôt la extern NSString* const kBaseUrlstructure. Deuxièmement, il définit un fichier unique pour les constantes. OMI, c'est faux car la plupart des classes n'ont pas besoin d'accéder à ces constantes ou pour accéder à toutes et le fichier peut devenir gonflé si toutes les constantes y sont déclarées. Une meilleure solution serait de modulariser les constantes en 3 couches différentes:

  1. Couche système: SystemConstants.hou AppConstants.h qui décrit les constantes à portée globale, auxquelles toutes les classes du système peuvent accéder. Déclarez ici uniquement les constantes auxquelles il faut accéder à partir de différentes classes non liées.

  2. Couche module / sous-système:, ModuleNameConstants.hdécrit un ensemble de constantes typiques d'un ensemble de classes associées, à l'intérieur d'un module / sous-système.

  3. Couche de classe: les constantes résident dans la classe et ne sont utilisées que par elle.

Seuls 1,2 sont liés à la question.


0

Une approche que j'ai utilisée auparavant consiste à créer un fichier Settings.plistet à le charger NSUserDefaultsau lancement en utilisant registerDefaults:. Vous pouvez ensuite accéder à son contenu avec les éléments suivants:

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

Bien que je n'ai pas fait de développement Android, il semble que cela soit analogue au fichier de ressources de chaînes que vous avez décrit. Le seul inconvénient est que vous ne pouvez pas utiliser le préprocesseur pour permuter entre les paramètres (par exemple en DEBUGmode). Je suppose que vous pouvez cependant charger un fichier différent.

NSUserDefaults Documentation.


9
N'est-ce pas un peu exagéré quand tout ce que vous voulez est une constante? Et aussi, pourquoi la mettre dans un fichier potentiellement modifiable? (Surtout quand c'est quelque chose d'aussi critique que l'adresse IP de votre serveur maître, sans lequel votre application ne fonctionne pas).
Cyrille

Je pense que cette approche a plusieurs avantages, l'être le plus important que vos paramètres sont retournés dans le format correct ( NSString, NSNumber, etc.). Bien sûr, vous pouvez envelopper vos #defines pour faire la même chose, mais ils ne sont pas aussi faciles à modifier. L' plistinterface d'édition est également agréable. :) Bien que je convienne que vous ne devriez pas y mettre de trucs super secrets comme des clés de chiffrement, je ne suis pas trop préoccupé par les utilisateurs qui fouinent dans des endroits où ils ne devraient pas être - s'ils cassent l'application, c'est de leur propre faute .
Chris Doble

1
Bien sûr, je suis d'accord avec vos arguments. Comme vous le dites, j'emballe mes #defines pour renvoyer le bon type, mais j'ai l'habitude d'éditer de tels fichiers de constantes, car j'ai toujours appris à mettre des constantes globales comme celle-ci dans un fichier de constantes séparé, à l'époque où j'ai appris Pascal sur un vieux 286 :) Et quant à l'utilisateur qui fouille partout, je suis d'accord aussi, c'est de sa faute. C'est juste une question de goût, vraiment.
Cyrille

@Chris Doble: Non, les fichiers de ressources sous Android ne sont pas similaires à NSUserDefaults. SharedPreferences et Preferences sont l'équivalent Android de NSUserDefaults (bien que plus puissant que NSUserDefaults). Les ressources dans Android sont destinées à séparer la logique du contenu, comme pour la localisation, et pour de nombreuses autres utilisations.
mrd le

0

Pour un numéro, vous pouvez l'utiliser comme

#define MAX_HEIGHT 12.5

0

J'utiliserais un objet de configuration qui s'initialise à partir d'un fichier plist. Pourquoi déranger d'autres classes avec des éléments externes non pertinents?

J'ai créé eppz!settignsuniquement pour cette raison. Voir l'article Moyen simple mais avancé d'enregistrer dans NSUserDefaults pour incorporer les valeurs par défaut d'un fichierplist .

entrez la description de l'image ici


Vrai. Wordpress ne résout pas en quelque sorte ...: / ... allez quand même avec ce lien direct blog.eppz.eu/?p=926 : D
Geri Borbás
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.