Onde armazenar constantes globais em um aplicativo iOS?

111

A maioria dos modelos em meu aplicativo iOS consulta um servidor web. Eu gostaria de ter um arquivo de configuração armazenando a URL base do servidor. Vai parecer algo assim:

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

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

Comentando uma linha ou outra, posso alterar instantaneamente para qual servidor meus modelos apontam. Minha pergunta é: qual é a prática recomendada para armazenar constantes globais no iOS? Na programação Android, temos esse arquivo de recurso de strings integrado . Em qualquer Activity (o equivalente a um UIViewController ), podemos recuperar essas constantes de string com:

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

Eu queria saber se o iOS SDK tem um lugar análogo para armazenar constantes. Se não, qual é a melhor prática em Objective-C para fazer isso?

JoJo
fonte

Respostas:

145

Você também pode fazer um

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

em um arquivo de cabeçalho de "constantes", digamos constants.h. Então faça

#include "constants.h"

na parte superior de cada arquivo onde você precisa dessa constante.

Dessa forma, você pode alternar entre os servidores dependendo das sinalizações do compilador, como em:

#ifdef DEBUG
    #define kBaseURL @"http://192.168.0.123/"
#else
    #define kBaseURL @"http://myproductionserver.com/"
#endif
Cyrille
fonte
Eu uso a "constants.h"abordagem, declarando staticvariáveis ​​com base em #ifdef VIEW_CONSTANTS ... #endif. Portanto, tenho um arquivo de constantes para todo o aplicativo, mas cada um dos meus outros arquivos de código contém #definediferentes conjuntos de constantes a serem incluídos antes #includedo arquivo de constantes (interrompe todos os avisos do compilador "definidos, mas não usados").
2
Encontrei dois problemas com essa solução. Primeiro, quando usei #decalare, recebi um erro de compilação dizendo " declaração de diretiva de pré-processamento inválida ". Então mudei para #define. O outro problema é usar a constante. Eu queria criar outra constante com static NSString* const fullUrl = [NSString stringWithFormat:@"%@%@", kbaseUrl, @"script.php"], mas aparentemente é ilegal criar consts com uma expressão. Recebo o erro "o elemento inicializador não é constante ".
JoJo
1
@Cyrille Android é realmente interessante de praticar, existem algumas possibilidades que você não poderia imaginar no iOS! De qualquer forma, obrigado pela resposta
Klefevre
8
Prefira const em vez de #define onde possível - você obtém uma melhor verificação em tempo de compilação e a depuração funciona melhor.
occulus
2
@AnsonYao geralmente, quando isso acontece comigo, esqueci de remover um ponto-e-vírgula da #define, como #define kBaseURL @"http://192.168.0.123/";
Gyfis
168

Bem, você quer a declaração local para as interfaces às quais se relaciona - o arquivo de constantes do aplicativo não é uma coisa boa.

Além disso, é preferível simplesmente declarar um extern NSString* constsímbolo, em vez de usar um #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

Além da omissão da declaração Extern compatível com C ++, isso é o que geralmente você verá sendo usado nos frameworks Obj-C da Apple.

Se a constante precisa ser visível para apenas um arquivo ou função, então static NSString* const baseUrlem seu *.mé bom.

justin
fonte
26
Não sei por que a resposta aceita tem 40 votos para defender #define - const é realmente melhor.
outubro
1
Definitivamente const NSString é melhor do que #define, esta deve ser a resposta aceita. #define cria uma nova string toda vez que o valor definido é usado.
jbat100
1
@ jbat100 Não creio que crie uma nova string. Acho que o compilador detecta se seu código cria a mesma string estática 300.000 vezes e só a cria uma vez. @"foo"não é o mesmo que [[NSString alloc] initWithCString:"foo"].
Abhi Beckert
@AbhiBeckert acho que o que jbat estava tentando fazer é que é possível acabar com duplicatas de sua constante quando ela #defineé usada (ou seja, a igualdade do ponteiro pode falhar) - não que uma expressão literal NSString produza um temporário sempre que executada.
justin de
1
Concordo que #define é uma má ideia, só queria corrigir o erro que ele cometeu de que criará vários objetos. Além disso, a igualdade do ponteiro não pode ser considerada nem mesmo para constantes. Ele pode ser carregado de NSUserDefaults ou algo assim. Sempre use isEqual :.
Abhi Beckert
39

A maneira como defino constantes globais:


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

Em seguida, em seu arquivo {$ APP} -Prefix.pch:

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

Se você tiver problemas, primeiro certifique-se de que a opção Cabeçalho do prefixo de pré-compilação esteja definida como NÃO.

Piotr Tomasik
fonte
5

Você também pode concatenar constantes de string como esta:

  #define kBaseURL @"http://myServer.com"
  #define kFullURL kBaseURL @"/api/request"
Martin Reichl
fonte
4

Eu acho que outra maneira de fazer isso é muito mais simples e você apenas incluirá isso nos arquivos em que precisa que eles sejam incluídos, não em TODOS os arquivos, como com o arquivo de prefixo .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 */

Depois disso, você inclui esse arquivo de cabeçalho no arquivo de cabeçalho que deseja. Você o inclui no arquivo de cabeçalho para a classe específica em que deseja que ele seja incluído:

#include "Constants.h"
Vladimir Despotovic
fonte
Em meus testes, constantes constantes estáticas não podem ser usadas no depurador (lldb do Xcode). "error: use of undeclared identifier .."
jk7
3
  1. Eu defino a constante global no arquivo YOURPROJECT-Prefix.pch.
  2. #define BASEURl @"http://myWebService.appspot.com/xyz/xx"
  3. em seguida, em qualquer lugar do projeto para usar BASEURL:

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

Atualizado: No Xcode 6 você não encontrará o arquivo .pch padrão criado em seu projeto. Portanto, use o arquivo PCH no Xcode 6 para inserir o arquivo .pch em seu projeto.

Atualizações: Para SWIFT

  1. Crie um novo arquivo Swift [vazio sem classe] diga [AppGlobalMemebers]
  2. & Declarar / definir imediatamente o membro

    Exemplo:

    var STATUS_BAR_GREEN : UIColor  = UIColor(red: 106/255.0, green: 161/255.0, blue: 7/255.0, alpha: 1)  //
    1. Se você deseja definir o membro global do aplicativo em qualquer arquivo de classe, diga Appdelegate ou Singleton class ou qualquer um, declare determinado membro acima da definição de classe
Yogesh Lolusare
fonte
2

Declarações globais são interessantes, mas, para mim, o que mudou profundamente minha maneira de codificar foi ter instâncias globais de classes. Levei alguns dias para entender realmente como trabalhar com isso, então rapidamente resumi aqui

Eu uso instâncias globais de classes (1 ou 2 por projeto, se necessário), para reagrupar o acesso aos dados principais ou algumas lógicas comerciais.

Por exemplo, se você deseja que um objeto central manipule todas as mesas de restaurante, você cria seu objeto na inicialização e é isso. Este objeto pode manipular acessos ao banco de dados OU manipulá-lo na memória se você não precisar salvá-lo. É centralizado, você mostra apenas interfaces úteis ...!

É uma grande ajuda, orientada a objetos e uma boa maneira de colocar todas as suas coisas no mesmo lugar

Algumas linhas de código:

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

e implementação de objeto:

@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 usá-lo é muito simples:

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

Gregoire Mulliez
fonte
3
O nome técnico para este padrão de design é Singleton. en.wikipedia.org/wiki/Singleton_pattern
Basil Bourque
Manter dados estáticos (classes não estáticas) no sharedmanager não é uma boa ideia.
Onder OZCAN
1

A resposta aceita tem 2 pontos fracos. Primeiro, como outros apontaram, o uso #defineé mais difícil de depurar, use a extern NSString* const kBaseUrlestrutura. Segundo, ele define um único arquivo para constantes. IMO, isso está errado porque a maioria das classes não precisa de acesso a essas constantes ou para acessar todas elas, mais o arquivo pode ficar inchado se todas as constantes forem declaradas lá. Uma solução melhor seria modularizar constantes em 3 camadas diferentes:

  1. Camada do sistema: SystemConstants.hou AppConstants.h que descreve constantes em escopo global, que podem ser acessadas por qualquer classe do sistema. Declare aqui apenas as constantes que devem ser acessadas de classes diferentes que não estão relacionadas.

  2. Camada ModuleNameConstants.hde módulo / subsistema : descreve um conjunto de constantes típicas de um conjunto de classes relacionadas, dentro de um módulo / subsistema.

  3. Camada de classe: as constantes residem na classe e são usadas apenas por ela.

Apenas 1,2 estão relacionados à pergunta.

Stefan
fonte
0

Uma abordagem que usei antes é criar um arquivo Settings.pliste carregá-lo NSUserDefaultsao iniciar usando registerDefaults:. Você pode acessar seu conteúdo com o seguinte:

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

Embora eu não tenha feito nenhum desenvolvimento para Android, parece que isso é análogo ao arquivo de recurso de strings que você descreveu. A única desvantagem é que você não pode usar o pré-processador para alternar entre as configurações (por exemplo, no DEBUGmodo). Suponho que você possa carregar um arquivo diferente.

NSUserDefaults documentação.

Chris Doble
fonte
9
Não é um pouco exagerado quando tudo o que você quer é uma constante? E também, por que colocá-la em um arquivo potencialmente modificável? (Especialmente quando é algo tão crítico quanto o IP do seu servidor mestre, sem o qual seu aplicativo não funciona).
Cyrille de
Eu sinto que esta abordagem tem várias vantagens, sendo as mais significativas que as configurações são retornadas no formato correto ( NSString, NSNumber, etc.). Claro, você poderia embrulhar seus programas #definepara fazer a mesma coisa, mas eles não são tão fáceis de editar. A plistinterface de edição também é boa. :) Embora eu concorde que você não deve colocar coisas supersecretas como chaves de criptografia lá, não estou muito preocupado com os usuários que estão bisbilhotando em lugares onde não deveriam estar - se eles quebrarem o aplicativo, é sua própria culpa .
Chris Doble
1
Claro, concordo com seus argumentos. Como você disse, envolvo meus #defines para retornar o tipo correto, mas estou acostumado a editar esses arquivos de constantes, já que sempre aprendi a colocar constantes globais como esse em um arquivo de constantes separado, desde os dias em que aprendi Pascal em um velho 286 :) E quanto ao usuário que bisbilhota em todos os lugares, eu também concordo, a culpa é dele. É só uma questão de gosto, na verdade.
Cyrille de
@Chris Doble: Não, os arquivos de recursos no Android não são semelhantes aos NSUserDefaults. SharedPreferences e Preferences são o Android equivalente a NSUserDefaults (embora mais poderosos do que NSUserDefaults). Os recursos no Android têm como objetivo separar a lógica do conteúdo, para localização e para muitos outros usos.
mrd
0

Para um número, você pode usá-lo como

#define MAX_HEIGHT 12.5
Gihan
fonte