¿Dónde almacenar constantes globales en una aplicación iOS?


111

La mayoría de los modelos de mi aplicación iOS consultan un servidor web. Me gustaría tener un archivo de configuración que almacene la URL base del servidor. Se verá algo como esto:

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

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

Al comentar una línea u otra, puedo cambiar instantáneamente a qué servidor apuntan mis modelos. Mi pregunta es, ¿cuál es la mejor práctica para almacenar constantes globales en iOS? En la programación de Android, tenemos este archivo de recursos de cadenas integrado . En cualquier actividad (el equivalente de un UIViewController ), podemos recuperar esas constantes de cadena con:

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

Me preguntaba si el SDK de iOS tiene un lugar análogo para almacenar constantes. Si no es así, ¿cuál es la mejor práctica en Objective-C para hacerlo?

Respuestas:


145

También podrías hacer un

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

en un archivo de encabezado "constantes", digamos constants.h. Entonces hazlo

#include "constants.h"

en la parte superior de cada archivo donde necesite esta constante.

De esta manera, puede cambiar entre servidores según los indicadores del compilador, como en:

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

Utilizo el "constants.h"enfoque, declarando staticvariables basadas en #ifdef VIEW_CONSTANTS ... #endif. Así que tengo un archivo de constantes para toda la aplicación, pero cada uno de mis otros archivos de código tiene #definediferentes conjuntos de constantes para incluir antes de #include-ing el archivo de constantes (detiene todas esas advertencias del compilador "definidas pero no utilizadas").

2
Hay dos problemas que encontré con esta solución. Primero, cuando utilicé #decalare, recibí un error de compilación que decía " declaración de directiva de preprocesamiento no válida ". Así que lo cambié a #define. El otro problema es usar la constante. Quería crear otra constante con static NSString* const fullUrl = [NSString stringWithFormat:@"%@%@", kbaseUrl, @"script.php"], pero aparentemente es ilegal crear consts con una expresión. Recibo el error "el elemento inicializador no es constante ".
JoJo

1
@Cyrille Android es realmente interesante de practicar, ¡hay algunas posibilidades que no podrías imaginar en iOS! Gracias de todos modos por responder
klefevre

8
Prefiera const sobre #define siempre que sea posible: obtiene una mejor verificación en tiempo de compilación y la depuración funciona mejor.
occulus

2
@AnsonYao, por lo general, cuando me pasa eso, me olvido de quitar un punto y coma de #define, como #define kBaseURL @"http://192.168.0.123/";
Gyfis

168

Bueno, desea que la declaración sea local para las interfaces con las que se relaciona; el archivo de constantes de toda la aplicación no es algo bueno.

Además, es preferible simplemente declarar un extern NSString* constsímbolo, en lugar de usar 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

Aparte de la omisión de la declaración Extern compatible con C ++, esto es lo que generalmente verá utilizado en los marcos Obj-C de Apple.

Si la constante debe ser visible para un solo archivo o función, entonces static NSString* const baseUrlen su *.mes bueno.


26
No estoy seguro de por qué la respuesta aceptada tiene 40 votos para defender #define: const es de hecho mejor.
occulus

1
Definitivamente const NSString es mejor que #define, esta debería ser la respuesta aceptada. #define crea una nueva cadena cada vez que se usa el valor definido.
jbat100

1
@ jbat100 No creo que cree una nueva cadena. Creo que el compilador detecta si su código crea la misma cadena estática 300.000 veces y solo la creará una vez. @"foo"no es lo mismo que [[NSString alloc] initWithCString:"foo"].
Abhi Beckert

@AbhiBeckert Creo que el punto que jbat estaba tratando de hacer es que es posible terminar con duplicados de su constante cuando #definese usa (es decir, la igualdad de puntero podría fallar), no es que una expresión literal NSString produzca un temporal siempre que se ejecute.
Justin

1
Estoy de acuerdo en que #define es una mala idea, solo quería corregir el error que cometió de que creará múltiples objetos. Además, no se puede confiar en la igualdad de punteros ni siquiera para constantes. Puede que se cargue desde NSUserDefaults o algo así. Utilice siempre isEqual :.
Abhi Beckert

39

La forma en que defino las 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

Luego, en su archivo {$ APP} -Prefix.pch:

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

Si tiene algún problema, primero asegúrese de tener la opción Precompile Prefix Header configurada en NO.


5

También puede concatenar constantes de cadena como esta:

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

4

Creo que otra forma de hacer esto es mucho más simple y solo lo incluirá en los archivos en los que necesita que estén incluidos, no TODOS los archivos, como con el archivo de prefijo .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 */

Después de eso, incluya este archivo de encabezado en el archivo de encabezado que desee. Lo incluye en el archivo de encabezado para la clase específica en la que desea que se incluya:

#include "Constants.h"

En mis pruebas, las constantes constantes estáticas no se pueden utilizar en el depurador (lldb de Xcode). "error: use of undeclared identifier .."
jk7

3
  1. Defino constante global en el archivo YOURPROJECT-Prefix.pch.
  2. #define BASEURl @"http://myWebService.appspot.com/xyz/xx"
  3. luego en cualquier lugar del proyecto para usar BASEURL:

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

Actualizado: en Xcode 6 no encontrará el archivo .pch predeterminado creado en su proyecto. Entonces, use el archivo PCH en Xcode 6 para insertar el archivo .pch en su proyecto.

Actualizaciones: para SWIFT

  1. Cree un nuevo archivo Swift [vacío sin clase] diga [AppGlobalMemebers]
  2. & De inmediato declarar / definir miembro

    Ejemplo:

    var STATUS_BAR_GREEN : UIColor  = UIColor(red: 106/255.0, green: 161/255.0, blue: 7/255.0, alpha: 1)  //
    1. Si desea definir el miembro global de la aplicación en cualquier archivo de clase, diga Appdelegate o Singleton class o cualquiera, declare el miembro dado arriba de la definición de clase

2

Las declaraciones globales son interesantes pero, para mí, lo que cambió profundamente mi forma de codificar fue tener instancias globales de clases. Me tomó un par de días entender realmente cómo trabajar con él, así que lo resumí rápidamente aquí.

Utilizo instancias globales de clases (1 o 2 por proyecto, si es necesario), para reagrupar el acceso a los datos centrales o algunas lógicas comerciales.

Por ejemplo, si desea tener un objeto central que maneje todas las mesas del restaurante, cree su objeto al inicio y eso es todo. Este objeto puede manejar accesos a la base de datos O manejarlo en memoria si no necesita guardarlo. ¡Está centralizado, solo muestra interfaces útiles ...!

Es una gran ayuda, está orientada a objetos y es una buena manera de conseguir todas tus cosas en el mismo lugar.

Algunas líneas de código:

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

e implementación de objetos:

@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

para usarlo es realmente simple:

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


3
El nombre técnico de este patrón de diseño es Singleton. en.wikipedia.org/wiki/Singleton_pattern
Basil Bourque

Mantener datos estáticos (no clases estáticas) en sharedmanager no es una buena idea.
Onder OZCAN

1

La respuesta aceptada tiene 2 puntos débiles. Primero, como otros señalaron, su uso #definees más difícil de depurar, use en su lugar la extern NSString* const kBaseUrlestructura. En segundo lugar, define un solo archivo para constantes. En mi opinión, esto es incorrecto porque la mayoría de las clases no necesitan acceso a esas constantes o para acceder a todas ellas, más el archivo puede hincharse si todas las constantes se declaran allí. Una mejor solución sería modularizar constantes en 3 capas diferentes:

  1. Capa del sistema: SystemConstants.ho AppConstants.h que describe las constantes en el ámbito global, a las que puede acceder cualquier clase del sistema. Declare aquí solo aquellas constantes a las que se debe acceder desde diferentes clases que no están relacionadas.

  2. Capa ModuleNameConstants.hde módulo / subsistema : describe un conjunto de constantes que son típicas de un conjunto de clases relacionadas, dentro de un módulo / subsistema.

  3. Capa de clase: las constantes residen en la clase y solo las usa.

Solo 1,2 están relacionados con la pregunta.


0

Un enfoque que he usado antes es crear un archivo Settings.plisty cargarlo NSUserDefaultsal iniciarlo usando registerDefaults:. A continuación, puede acceder a su contenido con lo siguiente:

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

Si bien no he realizado ningún desarrollo de Android, parece que esto es análogo al archivo de recursos de cadenas que describió. El único inconveniente es que no puede usar el preprocesador para cambiar entre configuraciones (por ejemplo, en DEBUGmodo). Sin embargo, supongo que podrías cargar en un archivo diferente.

NSUserDefaults documentación.


9
¿No es un poco exagerado cuando todo lo que quieres es una constante? Y también, ¿por qué ponerlo en un archivo potencialmente modificable? (Especialmente cuando es algo tan crítico como la IP de su servidor maestro, sin el cual su aplicación no funciona).
Cyrille

Siento que este enfoque tiene varias ventajas, siendo la más significativa que la configuración se devuelven en el formato correcto ( NSString, NSNumber, etc.). Claro, podrías ajustar tus correos #defineelectrónicos para hacer lo mismo, pero no son tan fáciles de editar. La plistinterfaz de edición también es agradable. :) Si bien estoy de acuerdo en que no debes poner cosas súper secretas, como claves de cifrado, no me preocupan demasiado los usuarios que están hurgando en lugares donde no deberían estar; si rompen la aplicación, es su propia culpa. .
Chris Doble

1
Seguro, estoy de acuerdo con tus argumentos. Como dices, envuelvo mi #defines para devolver el tipo correcto, pero estoy acostumbrado a editar tales archivos de constantes, ya que siempre he aprendido a poner constantes globales como este en un archivo de constantes separado, desde los días en que aprendí Pascal en un viejo 286 :) Y en cuanto al usuario que hurga en todas partes, yo también estoy de acuerdo, es su culpa. En realidad, es solo una cuestión de gustos.
Cyrille

@Chris Doble: No, los archivos de recursos en Android no son similares a NSUserDefaults. SharedPreferences y Preferences son el equivalente de Android a NSUserDefaults (aunque más poderoso que NSUserDefaults). Los recursos en Android están destinados a separar la lógica del contenido, en cuanto a localización, y para muchos otros usos.
2014

0

Para un número puedes usarlo como

#define MAX_HEIGHT 12.5

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.