Constantes no Objective-C

1002

Estou desenvolvendo um aplicativo Cocoa e estou usando constantes NSStrings como formas de armazenar nomes-chave para minhas preferências.

Entendo que é uma boa ideia, pois permite a troca fácil de chaves, se necessário.
Além disso, é toda a noção de 'separar seus dados da sua lógica'.

Enfim, existe uma boa maneira de definir essas constantes uma vez para todo o aplicativo?

Tenho certeza de que existe uma maneira fácil e inteligente, mas agora minhas aulas redefinem as que eles usam.

Allyn
fonte
7
OOP é sobre agrupar seus dados com sua lógica. O que você está propondo é apenas uma boa prática de programação, ou seja, facilitando a alteração do seu programa.
Raffi Khatchadourian

Respostas:

1287

Você deve criar um arquivo de cabeçalho como

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

(você pode usar, em externvez de, FOUNDATION_EXPORTse seu código não for usado em ambientes C / C ++ mistos ou em outras plataformas)

Você pode incluir esse arquivo em cada arquivo que usa as constantes ou no cabeçalho pré-compilado do projeto.

Você define essas constantes em um arquivo .m como

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

Constants.m deve ser adicionado ao destino do aplicativo / estrutura para que ele seja vinculado ao produto final.

A vantagem de usar constantes de string em vez de #defineconstantes é que você pode testar a igualdade usando a comparação de ponteiro ( stringInstance == MyFirstConstant), que é muito mais rápida que a comparação de string ( [stringInstance isEqualToString:MyFirstConstant]) (e mais fácil de ler, IMO).

Barry Wark
fonte
67
Para uma constante inteira, seria: extern int const MyFirstConstant = 1;
21415 Dan Morgan
180
No geral, ótima resposta, com uma ressalva evidente: você NÃO deseja testar a igualdade de cadeias de caracteres com o operador == no Objective-C, pois ele testa o endereço da memória. Sempre use -isEqualToString: para isso. Você pode facilmente obter uma instância diferente comparando MyFirstConstant e [NSString stringWithFormat: MyFirstConstant]. Não faça suposições sobre qual instância de uma string você possui, mesmo com literais. (Em qualquer caso, #define é um "pré-processador diretiva", e é substituído antes da compilação, então de qualquer modo o compilador vê um literal de cadeia no final.)
Quinn Taylor
74
Nesse caso, não há problema em usar == para testar a igualdade com a constante, se ele realmente for usado como um símbolo constante (ou seja, o símbolo MyFirstConstant em vez de uma string contendo @ "MyFirstConstant" é usado). Um número inteiro pode ser usado em vez de uma string neste caso (realmente, é isso que você está fazendo - usando o ponteiro como um número inteiro), mas o uso de uma string constante facilita a depuração um pouco, pois o valor da constante tem um significado legível por humanos .
Barry Wark
17
+1 em "Constantes.m deve ser adicionado ao destino do aplicativo / estrutura para que ele seja vinculado ao produto final". Salvou minha sanidade. @ amok, faça "Obter informações" no arquivo Constants.m e escolha a guia "Destinos". Verifique se está marcado para o (s) destino (s) relevante (s).
PEZ
73
@ Barry: No cacau, eu já vi várias classes que definem suas NSStringpropriedades em copyvez de retain. Como tal, eles poderiam (e deveriam) manter uma instância diferente de sua NSString*constante e a comparação direta de endereços de memória falharia. Além disso, eu presumiria que qualquer implementação razoavelmente ótima de -isEqualToString:verificaria a igualdade de ponteiros antes de entrar na comparação detalhada de caracteres.
Ben Mosher
280

Caminho mais fácil:

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

Melhor maneira:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

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

Um benefício do segundo é que alterar o valor de uma constante não causa uma reconstrução de todo o programa.

Andrew Grant
fonte
12
Eu pensei que você não deveria mudar o valor das constantes.
Ruipacheco 28/12/2009
71
Andrew está se referindo a alterar o valor da constante durante a codificação, não enquanto o aplicativo estiver em execução.
Randall
7
Existe algum valor agregado em fazer extern NSString const * const MyConstant, ou seja, torná-lo um ponteiro constante para um objeto constante em vez de apenas um ponteiro constante?
Hari Karam Singh
4
O que acontece, se eu usar essa declaração no arquivo de cabeçalho, NSString estático * const kNSStringConst = @ "const value"; Qual é a diferença entre não declarar e iniciar separadamente nos arquivos .hem.
karim
4
@ Dogweather - Algum lugar onde apenas o compilador sabe a resposta. IE, se você quisesse incluir em um menu sobre o compilador usado para compilar uma compilação de um aplicativo, você poderia colocá-lo lá, pois o código compilado, caso contrário, não teria conhecimento. Não consigo pensar em muitos outros lugares. As macros certamente não devem ser usadas em muitos lugares. E se eu tivesse #define MY_CONST 5 e em outro lugar #define MY_CONST_2 25. O resultado é que você pode acabar tendo um erro de compilador ao tentar compilar 5_2. Não use #define para constantes. Use const para constantes.
ArtOfWarfare
190

Há também uma coisa a mencionar. Se você precisar de uma constante não global, use a staticpalavra-chave

Exemplo

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

Por causa da staticpalavra - chave, essa const não é visível fora do arquivo.


Correção menor por @QuinnTaylor : variáveis ​​estáticas são visíveis em uma unidade de compilação . Geralmente, esse é um único arquivo .m (como neste exemplo), mas pode ser mordido se você o declarar em um cabeçalho incluído em outro lugar, pois você receberá erros de vinculador após a compilação

kompozer
fonte
41
Correção menor: variáveis ​​estáticas são visíveis dentro de uma unidade de compilação . Geralmente, esse é um único arquivo .m (como neste exemplo), mas pode mordê-lo se você o declarar em um cabeçalho que está incluído em outro lugar, pois você receberá erros do vinculador após a compilação.
Quinn Taylor
Se eu não usar a palavra-chave estática, o kNSStringConst estará disponível durante todo o projeto?
Danyal Aytekin 07/11
2
Ok, verifiquei ... O Xcode não fornece preenchimento automático para outros arquivos se você deixar a estática desativada, mas tentei colocar o mesmo nome em dois lugares diferentes e reproduzi os erros do vinculador de Quinn.
Danyal Aytekin
1
estático em um arquivo de cabeçalho não causa problemas no vinculador. No entanto, cada unidade de compilação, incluindo o arquivo de cabeçalho, obterá sua própria variável estática; portanto, você obtém 100 delas se incluir o cabeçalho de 100 arquivos .m.
precisa saber é o seguinte
@kompozer Em que parte do arquivo .m você coloca isso?
Basil Bourque
117

A resposta aceita (e correta) diz que "você pode incluir esse arquivo [Constants.h] ... no cabeçalho pré-compilado do projeto".

Como iniciante, tive dificuldade em fazer isso sem maiores explicações - veja como: No arquivo YourAppNameHere-Prefix.pch (este é o nome padrão do cabeçalho pré-compilado no Xcode), importe seu Constants.h dentro do #ifdef __OBJC__bloco .

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

Observe também que os arquivos Constants.h e Constants.m não devem conter absolutamente mais nada, exceto o que está descrito na resposta aceita. (Sem interface ou implementação).

Victor Van Hee
fonte
Eu fiz isso, mas alguns arquivos geram erro na compilação "Uso do identificador não declarado 'CONSTANTSNAME' Se eu incluir o constant.h no arquivo que gera o erro, ele funcionará, mas não é isso que eu quero fazer. Limpei, desligamento Xcode e de construção e ainda problemas ... alguma idéia?
J3RM
50

Geralmente estou usando o caminho postado por Barry Wark e Rahul Gupta.

Embora eu não goste de repetir as mesmas palavras nos arquivos .he .m. Observe que no exemplo a seguir a linha é quase idêntica nos dois arquivos:

// file.h
extern NSString* const MyConst;

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

Portanto, o que eu gosto de fazer é usar algumas máquinas do pré-processador C. Deixe-me explicar através do exemplo.

Eu tenho um arquivo de cabeçalho que define a 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

No meu par .h / .m, onde desejo definir a constante, faço o seguinte:

// 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, eu tenho todas as informações sobre as constantes no arquivo .h.

Krizz
fonte
Hmm, há um pouco de advertência, no entanto, você não pode usar esta técnica como esta se o arquivo de cabeçalho for importado para o cabeçalho pré-compilado, porque não carregará o arquivo .h no arquivo .m porque já foi compilado. Há uma maneira embora - veja a minha resposta (desde que eu não posso colocar o código agradável nos comentários.
Scott Little
Não consigo fazer isso funcionar. Se eu colocar #define SYNTHESIZE_CONSTS antes de #import "myfile.h", ele fará o NSString * ... tanto no .he no .m (verificado usando a visualização do assistente e o pré-processador). Lança erros de redefinição. Se eu colocá-lo após #import "myfile.h", ele faz o NSString externo * ... nos dois arquivos. Em seguida, lança erros "Símbolo indefinido".
arsenius 24/01
28

Eu próprio tenho um cabeçalho dedicado a declarar constantes NSStrings usadas para preferências como:

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

Em seguida, declare-os no arquivo .m que acompanha:

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

Essa abordagem me serviu bem.

Editar: Observe que isso funciona melhor se as strings forem usadas em vários arquivos. Se apenas um arquivo o usar, você poderá fazê-lo #define kNSStringConstant @"Constant NSString"no arquivo .m que usa a string.

MaddTheSane
fonte
25

Uma leve modificação na sugestão do @Krizz, para que funcione corretamente se o arquivo de cabeçalho das constantes for incluído na PCH, o que é bastante normal. Como o original é importado para a PCH, ele não será recarregado no .marquivo e, portanto, você não recebe símbolos e o vinculador está descontente.

No entanto, a seguinte modificação permite que ele funcione. É um pouco complicado, mas funciona.

Você vai precisar de 3 arquivos, .harquivo que tem as definições constantes, o .harquivo e o .marquivo, eu vou usar ConstantList.h, Constants.he Constants.m, respectivamente. o conteúdo de Constants.hsão simplesmente:

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

e o Constants.marquivo se parece com:

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

Finalmente, o ConstantList.harquivo possui as declarações reais e isso é tudo:

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

Algumas coisas a serem observadas:

  1. Eu tive que redefinir a macro no .marquivo após #undef inseri-la na macro a ser usada.

  2. Eu também tive que usar em #includevez disso #importpara que funcionasse corretamente e evitar que o compilador visse os valores pré-compilados anteriormente.

  3. Isso exigirá uma recompilação do seu PCH (e provavelmente todo o projeto) sempre que quaisquer valores forem alterados, o que não acontece se forem separados (e duplicados) normalmente.

Espero que seja útil para alguém.

Scott Little
fonte
1
O uso de #include corrigiu essa dor de cabeça para mim.
Ramsel
Isso tem alguma perda de desempenho / memória quando comparado à resposta aceita?
Gyfis 31/05
Em resposta ao desempenho comparado à resposta aceita, não há. É efetivamente exatamente a mesma coisa do ponto de vista do compilador. Você acaba com as mesmas declarações. Eles seriam exatamente os mesmos se você substituísse o externacima por FOUNDATION_EXPORT.
22615 Scott Scott
14
// Prefs.h
extern NSString * const RAHUL;

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

Como a Abizer disse, você pode colocá-lo no arquivo PCH. Outra maneira que não é tão suja é criar um arquivo de inclusão para todas as suas chaves e depois incluir no arquivo em que você está usando as chaves ou incluí-lo na PCH. Com eles em seu próprio arquivo de inclusão, isso fornece pelo menos um lugar para procurar e definir todas essas constantes.

Grant Limberg
fonte
11

Se você deseja algo como constantes globais; Uma maneira rápida e suja é colocar as constantes declarações no pcharquivo.

Abizern
fonte
7
Editar o .pch geralmente não é a melhor ideia. Você precisará encontrar um local para realmente definir a variável, quase sempre um arquivo .m, portanto, faz mais sentido declá- la no arquivo .h correspondente. A resposta aceita para criar um par Constants.h / m é boa se você precisar deles durante todo o projeto. Eu geralmente coloco constantes o mais abaixo possível da hierarquia, com base em onde elas serão usadas.
Quinn Taylor
8

Tente usar um método de classe:

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

Eu uso algumas vezes.

groumpf
fonte
6
Um método de classe não é uma constante. Ele tem um custo em tempo de execução e nem sempre pode retornar o mesmo objeto (se o implementar dessa maneira, mas você não necessariamente o implementou dessa maneira), o que significa que você deve usar isEqualToString:a comparação, o que é um custo adicional em tempo de execução. Quando você quiser constantes, faça constantes.
31526 Peter Hosey
2
@ Peter Hosey, enquanto seus comentários estão certos, consideramos esse desempenho uma vez por LOC ou mais em idiomas de "nível superior", como Ruby, sem se preocupar com isso. Não estou dizendo que você não está certo, mas apenas comentando sobre como os padrões são diferentes em diferentes "mundos".
Dan Rosenstark
1
Verdadeiro sobre Ruby. A maior parte do desempenho que as pessoas codificam é bastante desnecessária para o aplicativo típico.
Peter DeWeese
8

Se você gosta de constante de espaço para nome, pode aproveitar struct, sexta-feira , P&R 19-08-2011: Constantes e funções com espaço para nome

// 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"
};
onmyway133
fonte
1
Uma ótima coisa! Porém, no ARC, você precisará prefixar todas as variáveis ​​na declaração struct com __unsafe_unretainedqualificador para fazê-lo funcionar.
Cemen 2/08/15
7

Eu uso uma classe singleton, para que eu possa zombar da classe e alterar as constantes, se necessário para o teste. A classe constantes fica assim:

#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

E é usado assim (observe o uso de uma abreviação para as constantes c - ele salva a digitação [[Constants alloc] init]toda 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
Howard Lovatt
fonte
1

Se você deseja chamar algo assim a NSString.newLine;partir do objetivo c e deseja que seja constante estática, é possível criar algo assim rapidamente:

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

E você tem uma definição constante legível e disponível em um tipo de sua escolha enquanto o estilo é limitado ao contexto do tipo.

Renetik
fonte