Constantes en Objective-C

Estoy desarrollando un Cacao aplicación, y estoy usando constante NSStrings como formas de almacenar nombres de claves para mis preferencias.

Entiendo que esta es una buena idea porque permite un fácil cambio de claves si es necesario.
Además, es toda la noción de 'separar sus datos de su lógica'.

De todos modos, ¿hay una buena manera de hacer que estas constantes se definan una vez para toda la aplicación?

Estoy seguro de que hay una manera fácil e inteligente, pero ahora mismo mis clases simplemente redefinen las que usan.

preguntado el 11 de febrero de 09 a las 19:02

OOP se trata de agrupamiento tu información con tu lógica. Lo que está proponiendo es solo una buena práctica de programación, es decir, hacer que su programa sea fácil de cambiar. -

14 Respuestas

Deberías crear un archivo de encabezado como

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

(puedes usar extern en lugar de FOUNDATION_EXPORT si su código no se utilizará en entornos mixtos C / C ++ o en otras plataformas)

Puede incluir este archivo en cada archivo que use las constantes o en el encabezado precompilado para el proyecto.

Defina estas constantes en un archivo .m como

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

Constants.m debe agregarse al destino de su aplicación / marco para que esté vinculado al producto final.

La ventaja de usar constantes de cadena en lugar de #define'd constantes es que puede probar la igualdad usando la comparación de punteros (stringInstance == MyFirstConstant) que es mucho más rápido que la comparación de cadenas ([stringInstance isEqualToString:MyFirstConstant]) (y más fácil de leer, en mi opinión).

Respondido 29 Abr '13, 12:04

Para una constante entera sería: extern int const MyFirstConstant = 1; - Dan Morgan

En general, excelente respuesta, con una advertencia evidente: NO desea probar la igualdad de cadenas con el operador == en Objective-C, ya que prueba la dirección de memoria. Utilice siempre -isEqualToString: para esto. Puede obtener fácilmente una instancia diferente comparando MyFirstConstant y [NSString stringWithFormat: MyFirstConstant]. No haga suposiciones sobre qué instancia de una cadena tiene, incluso con literales. (En cualquier caso, #define es una "directiva de preprocesador", y se sustituye antes de la compilación, por lo que de cualquier forma el compilador ve una cadena literal al final). quinn taylor

En este caso, está bien usar == para probar la igualdad con la constante, si realmente se usa como un símbolo constante (es decir, se usa el símbolo MyFirstConstant en lugar de una cadena que contenga @ "MyFirstConstant"). En este caso, se podría usar un número entero en lugar de una cadena (en realidad, eso es lo que está haciendo, usar el puntero como un número entero), pero usar una cadena constante hace que la depuración sea un poco más fácil ya que el valor de la constante tiene un significado legible por humanos. . - barry wark

+1 para "Constants.m debe agregarse al destino de su aplicación / marco para que esté vinculado al producto final". Salvó mi cordura. @amok, haz "Obtener información" en Constants.m y elige la pestaña "Objetivos". Asegúrese de que esté marcado para los objetivos relevantes. - PEZ

@Barry: En Cocoa, he visto una serie de clases que definen su NSString propiedades con copy en lugar de retain. Como tal, podrían (y deberían) tener una instancia diferente de su NSString* La comparación de direcciones de memoria constante y directa fallaría. Además, supongo que cualquier implementación razonablemente óptima de -isEqualToString: comprobaría la igualdad del puntero antes de entrar en el meollo de la comparación de caracteres. - ben mosher

La manera más fácil:

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

Mejor manera:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

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

Un beneficio del segundo es que cambiar el valor de una constante no causa una reconstrucción de todo el programa.

Respondido 11 Feb 09, 22:02

Pensé que no se suponía que debías cambiar el valor de las constantes. - ruipacheco

Andrew se refiere a cambiar el valor de la constante durante la codificación, no mientras la aplicación se está ejecutando. - Randall

¿Hay algún valor agregado al hacer extern NSString const * const MyConstant, es decir, ¿convertirlo en un puntero constante a un objeto constante en lugar de solo un puntero constante? - Hari honor

Qué sucede, si uso esta declaración en el archivo de encabezado, static NSString * const kNSStringConst = @ "const value"; ¿Cuál es la diferencia entre no declarar e init por separado en archivos .hy .m? - Karim

@Dogweather: un lugar donde solo el compilador conoce la respuesta. Es decir, si quisiera incluir en un menú acerca de qué compilador se usó para compilar la compilación de una aplicación, podría colocarlo allí, ya que de otro modo el código compilado no tendría ninguna forma de saberlo. No puedo pensar en muchos otros lugares. Las macros ciertamente no deberían usarse en muchos lugares. ¿Qué pasaría si tuviera #define MY_CONST 5 y en otro lugar #define MY_CONST_2 25. El resultado es que muy bien puede terminar con un error de compilación cuando intente compilar 5_2. No use #define para constantes. Utilice const para constantes. - ArtOfWarfare

También hay una cosa que mencionar. Si necesita una constante no global, debe usar static palabra clave.

Ejemplo

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

Debido a la static palabra clave, esta constante no es visible fuera del archivo.


Corrección menor por @QuinnTaylor: las variables estáticas son visibles dentro de un unidad de compilación. Por lo general, este es un solo archivo .m (como en este ejemplo), pero puede morderlo si lo declara en un encabezado que se incluye en otro lugar, ya que obtendrá errores del vinculador después de la compilación

contestado el 23 de mayo de 17 a las 13:05

Corrección menor: las variables estáticas son visibles dentro de un unidad de compilación. Por lo general, este es un solo archivo .m (como en este ejemplo), pero puede morderlo si lo declara en un encabezado que se incluye en otro lugar, ya que obtendrá errores del vinculador después de la compilación. - quinn taylor

Si no uso la palabra clave estática, ¿estará disponible kNSStringConst durante todo el proyecto? - danyal aytekin

Ok, solo verifiqué ... Xcode no proporciona autocompletado en otros archivos si dejas la estática apagada, pero intenté poner el mismo nombre en dos lugares diferentes y reproduje los errores del enlazador de Quinn. - danyal aytekin

static en un archivo de encabezado no da problemas de vinculador. Sin embargo, cada unidad de compilación, incluido el archivo de encabezado, obtendrá su propia variable estática, por lo que obtendrá 100 de ellos si incluye el encabezado de 100 archivos .m. - gnasher729

@kompozer ¿En qué parte del archivo .m coloca esto? - Albahaca bourque

La respuesta aceptada (y correcta) dice que "puede incluir este archivo [Constants.h] ... en el encabezado precompilado del proyecto".

Como novato, tuve dificultades para hacer esto sin más explicaciones; así es como: En su archivo YourAppNameHere-Prefix.pch (este es el nombre predeterminado para el encabezado precompilado en Xcode), importe su Constants.h dentro de #ifdef __OBJC__ bloquear.

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

También tenga en cuenta que los archivos Constants.hy Constants.m no deben contener absolutamente nada más, excepto lo que se describe en la respuesta aceptada. (Sin interfaz ni implementación).

Respondido el 31 de enero de 16 a las 17:01

Hice esto, pero algunos archivos arrojan un error en la compilación "Uso del identificador no declarado 'CONSTANTSNAME' Si incluyo el constant.h en el archivo que arroja el error, funciona, pero eso no es lo que quiero hacer. He limpiado, apagado xcode y build y aún problemas ... ¿alguna idea? - J3RM

Generalmente utilizo la forma publicada por Barry Wark y Rahul Gupta.

Aunque no me gusta repetir las mismas palabras en archivos .hy .m. Tenga en cuenta que en el siguiente ejemplo la línea es casi idéntica en ambos archivos:

// file.h
extern NSString* const MyConst;

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

Por lo tanto, lo que me gusta hacer es usar alguna maquinaria de preprocesador de C. Déjame explicarte con el ejemplo.

Tengo un archivo de encabezado que define 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

En mi par .h / .m donde quiero definir la constante, hago lo siguiente:

// 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, tengo toda la información sobre las constantes en el archivo .h solamente.

Respondido el 06 de enero de 12 a las 01:01

Hmm, hay una pequeña advertencia, sin embargo, no puede usar esta técnica de esta manera si el archivo de encabezado se importa en el encabezado precompilado, porque no cargará el archivo .h en el archivo .m porque ya estaba compilado. Sin embargo, hay una manera: vea mi respuesta (ya que no puedo poner un código agradable en los comentarios). Scott poco

No puedo hacer que esto funcione. Si pongo #define SYNTHESIZE_CONSTS antes de #import "myfile.h", hace NSString * ... tanto en .hy .m (comprobado usando la vista del asistente y el preprocesador). Lanza errores de redefinición. Si lo pongo después de #import "myfile.h", lo hace extern NSString * ... en ambos archivos. Luego arroja errores de "símbolo indefinido". - arsenio

Yo mismo tengo un encabezado dedicado a declarar NSStrings constantes utilizados para preferencias como esta:

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

Luego, declarándolos en el archivo .m adjunto:

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

Este enfoque me ha servido bien.

Editar: tenga en cuenta que esto funciona mejor si las cadenas se utilizan en varios archivos. Si solo un archivo lo usa, puede hacer #define kNSStringConstant @"Constant NSString" en el archivo .m que usa la cadena.

Respondido 26 ago 13, 05:08

Una leve modificación de la sugerencia de @Krizz, para que funcione correctamente si el archivo de encabezado de constantes se va a incluir en el PCH, lo cual es bastante normal. Dado que el original se importa al PCH, no se volverá a cargar en el .m archivo y, por lo tanto, no obtiene símbolos y el vinculador no está satisfecho.

Sin embargo, la siguiente modificación le permite funcionar. Es un poco complicado, pero funciona.

Necesitarás 3 archivos, .h archivo que tiene las definiciones constantes, el .h archivo y el .m archivo, usaré ConstantList.h, Constants.h y Constants.m, respectivamente. los contenidos de Constants.h son simplemente:

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

y Constants.m el archivo se ve así:

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

Finalmente, la ConstantList.h archivo tiene las declaraciones reales en él y eso es todo:

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

Un par de cosas a tener en cuenta:

  1. Tuve que redefinir la macro en el .m presentar después de #undefingiriéndolo para que se utilice la macro.

  2. Yo tambien tuve que usar #include en lugar de #import para que esto funcione correctamente y evitar que el compilador vea los valores precompilados previamente.

  3. Esto requerirá una recompilación de su PCH (y probablemente todo el proyecto) cada vez que se cambie algún valor, lo que no es el caso si se separan (y se duplican) de forma normal.

Espero que sea de ayuda para alguien.

Respondido el 03 de diciembre de 11 a las 00:12

El uso de #include me solucionó este dolor de cabeza. - OdieO

¿Tiene esto alguna pérdida de rendimiento / memoria en comparación con la respuesta aceptada? - Gyfis

En respuesta al rendimiento en comparación con la respuesta aceptada, no hay ninguna. Efectivamente, es exactamente lo mismo desde el punto de vista del compilador. Terminas con las mismas declaraciones. Serían EXACTAMENTE iguales si reemplazaras el extern arriba con el FOUNDATION_EXPORT. - Scott poco

// Prefs.h
extern NSString * const RAHUL;

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

Respondido el 28 de Septiembre de 11 a las 17:09

Como dijo Abizer, podría ponerlo en el archivo PCH. Otra forma que no es tan sucia es hacer un archivo de inclusión para todas sus claves y luego incluirlo en el archivo en el que está usando las claves, o incluirlo en el PCH. Con ellos en su propio archivo de inclusión, eso al menos le da un lugar para buscar y definir todas estas constantes.

Respondido 11 Feb 09, 22:02

Si quieres algo como constantes globales; Una forma rápida y sucia es poner las constantes declaraciones en el pch archivo.

Respondido el 07 de enero de 17 a las 09:01

Editar el .pch no suele ser la mejor idea. Tendrás que encontrar un lugar para definir la variable, casi siempre un archivo .m, por lo que tiene más sentido declarar en el archivo .h correspondiente. La respuesta aceptada de crear un par Constants.h / m es buena si los necesita en todo el proyecto. Por lo general, coloco las constantes lo más abajo posible en la jerarquía, en función de dónde se usarán. - quinn taylor

Intente usar un método de clase:

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

Lo uso a veces.

Respondido el 31 de enero de 16 a las 17:01

Un método de clase no es una constante. Tiene un costo en tiempo de ejecución y es posible que no siempre devuelva el mismo objeto (lo hará si lo implementa de esa manera, pero no necesariamente lo ha implementado de esa manera), lo que significa que debe usar isEqualToString: para la comparación, que es un costo adicional en tiempo de ejecución. Cuando desee constantes, haga constantes. - Pedro Hosey

@Peter Hosey, aunque sus comentarios son correctos, tomamos ese golpe de rendimiento una vez por LOC o más en lenguajes de "nivel superior" como Ruby sin preocuparnos por ello. No estoy diciendo que no tengas razón, sino que solo estoy comentando cómo los estándares son diferentes en diferentes "mundos". - dan rosenstark

Cierto en Ruby. La mayor parte del código de rendimiento de la gente es bastante innecesario para la aplicación típica. - Pedro De Weese

Si le gusta la constante de espacio de nombres, puede aprovechar la estructura, Friday Q&A 2011-08-19: Constantes y funciones de espacio de nombres

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

Respondido 12 ago 16, 12:08

¡Una gran cosa! Pero bajo ARC necesitará prefijar todas las variables en la declaración de estructura con __unsafe_unretained calificador para que funcione. - cemento

Utilizo una clase singleton, para poder burlarme de la clase y cambiar las constantes si es necesario para la prueba. La clase de constantes se ve así:

#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

Y se usa así (tenga en cuenta el uso de una abreviatura para las constantes c: ahorra escribir [[Constants alloc] init] cada vez):

#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

Respondido 16 Jul 12, 01:07

Si quieres llamar a algo como esto NSString.newLine; desde el objetivo c, y desea que sea una constante estática, puede crear algo como esto en rápido:

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

Y tiene una definición constante legible agradable, y disponible desde dentro de un tipo de su elección mientras está limitado al contexto del tipo.

Respondido el 21 de enero de 19 a las 17:01

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.