Wo können globale Konstanten in einer iOS-Anwendung gespeichert werden?

111

Die meisten Modelle in meiner iOS-App fragen einen Webserver ab. Ich hätte gerne eine Konfigurationsdatei, in der die Basis-URL des Servers gespeichert ist. Es wird ungefähr so ​​aussehen:

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

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

Durch Auskommentieren der einen oder anderen Zeile kann ich sofort ändern, auf welchen Server meine Modelle verweisen. Meine Frage ist, was ist die beste Vorgehensweise zum Speichern globaler Konstanten in iOS? In der Android-Programmierung haben wir diese integrierte String-Ressourcendatei . In jeder Aktivität (das Äquivalent eines UIViewControllers ) können wir diese Zeichenfolgenkonstanten abrufen mit:

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

Ich habe mich gefragt, ob das iOS SDK einen analogen Speicherort für Konstanten hat. Wenn nicht, was ist die beste Vorgehensweise in Objective-C, um dies zu tun?

JoJo
quelle

Antworten:

145

Sie könnten auch eine tun

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

Sagen wir in einer "Konstanten" -Headerdatei constants.h. Dann mach

#include "constants.h"

oben in jeder Datei, in der Sie diese Konstante benötigen.

Auf diese Weise können Sie abhängig von den Compiler-Flags zwischen Servern wechseln, wie in:

#ifdef DEBUG
    #define kBaseURL @"http://192.168.0.123/"
#else
    #define kBaseURL @"http://myproductionserver.com/"
#endif
Cyrille
quelle
Ich benutze den "constants.h"Ansatz und deklariere staticVariablen basierend auf #ifdef VIEW_CONSTANTS ... #endif. Ich habe also eine anwendungsweite Konstantendatei, aber jede meiner anderen Codedateien muss #defineunterschiedliche Konstantensätze enthalten, bevor #includedie Konstantendatei erstellt wird (stoppt alle "definierten, aber nicht verwendeten" Compiler-Warnungen).
2
Es gibt zwei Probleme, auf die ich bei dieser Lösung gestoßen bin. Als ich es benutzte #decalare, bekam ich zuerst einen Kompilierungsfehler mit der Aufschrift " Ungültige Vorverarbeitungsrichtlinie deklarieren ". Also habe ich es #definestattdessen geändert . Das andere Problem ist die Verwendung der Konstante. Ich wollte eine andere Konstante mit erstellen static NSString* const fullUrl = [NSString stringWithFormat:@"%@%@", kbaseUrl, @"script.php"], aber anscheinend ist es illegal, Konstanten mit einem Ausdruck zu erstellen. Ich erhalte die Fehlermeldung " Initialisierungselement ist nicht konstant ".
JoJo
1
@Cyrille Android ist wirklich interessant zu üben, es gibt einige Möglichkeiten, die Sie sich unter iOS nicht vorstellen können!
Trotzdem
8
Ziehen Sie const nach Möglichkeit #define vor - Sie erhalten eine bessere Überprüfung der Kompilierungszeit, und das Debuggen funktioniert besser.
Occulus
2
@AnsonYao normalerweise, wenn mir das passiert, habe ich vergessen, ein Semikolon aus dem #define zu entfernen, wie #define kBaseURL @"http://192.168.0.123/";
Gyfis
168

Nun, Sie möchten die Deklaration lokal für die Schnittstellen, auf die sie sich bezieht - die app-weite Konstantendatei ist keine gute Sache.

Außerdem ist es vorzuziehen, einfach ein extern NSString* constSymbol zu deklarieren , anstatt ein #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

Abgesehen vom Weglassen der C ++ - kompatiblen externen Deklaration wird dies im Allgemeinen in den Obj-C-Frameworks von Apple verwendet.

Wenn die Konstante nur für eine Datei oder Funktion sichtbar sein muss, ist static NSString* const baseUrlin Ihrer *.mgut.

Justin
quelle
26
Ich bin mir nicht sicher, warum die akzeptierte Antwort 40 Stimmen für die Befürwortung von #define hat - const ist in der Tat besser.
Occulus
1
Auf jeden Fall ist const NSString besser als #define, dies sollte die akzeptierte Antwort sein. #define erstellt jedes Mal eine neue Zeichenfolge, wenn der definierte Wert verwendet wird.
jbat100
1
@ jbat100 Ich glaube nicht, dass es einen neuen String erstellt. Ich denke, der Compiler erkennt, ob Ihr Code 300.000 Mal dieselbe statische Zeichenfolge erstellt, und erstellt sie nur einmal. @"foo"ist nicht dasselbe wie [[NSString alloc] initWithCString:"foo"].
Abhi Beckert
@AbhiBeckert Ich denke, der Punkt, den jbat anstrebte, ist, dass es möglich ist, Duplikate Ihrer Konstanten zu erhalten, wenn sie #defineverwendet werden (dh Zeigergleichheit könnte fehlschlagen) - nicht, dass ein NSString-Literalausdruck bei jeder Ausführung eine temporäre erzeugt.
Justin
1
Ich bin damit einverstanden, dass #define eine schlechte Idee ist. Ich wollte nur den Fehler korrigieren, den er gemacht hat, dass mehrere Objekte erstellt werden. Auch bei Konstanten kann man sich nicht auf die Zeigergleichheit verlassen. Es könnte von NSUserDefaults oder so geladen werden. Verwenden Sie immer isEqual:.
Abhi Beckert
39

So definiere ich globale Konstanten:


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

Dann in Ihrer {$ APP} -Prefix.pch-Datei:

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

Wenn Probleme auftreten, stellen Sie zunächst sicher, dass die Option Precompile Prefix Header auf NO gesetzt ist.

Piotr Tomasik
quelle
5

Sie können auch Zeichenfolgenkonstanten wie folgt verketten:

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

Ich denke, dass ein anderer Weg, dies zu tun, viel einfacher ist und Sie ihn einfach in Dateien einfügen, in denen Sie ihn benötigen, nicht in ALLE Dateien, wie bei der .pch-Präfixdatei:

#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 */

Danach fügen Sie diese Header-Datei in die gewünschte Header-Datei ein. Sie fügen es in die Header-Datei für die bestimmte Klasse ein, in die es aufgenommen werden soll:

#include "Constants.h"
Vladimir Despotovic
quelle
In meinen Tests können statische Konstantenkonstanten im Debugger (Xcodes lldb) nicht verwendet werden. "error: use of undeclared identifier .."
jk7
3
  1. Ich definiere eine globale Konstante in der Datei YOURPROJECT-Prefix.pch.
  2. #define BASEURl @"http://myWebService.appspot.com/xyz/xx"
  3. dann irgendwo im Projekt, um BASEURL zu verwenden:

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

Aktualisiert: In Xcode 6 finden Sie keine in Ihrem Projekt erstellte Standard-PCH-Datei. Verwenden Sie daher bitte die PCH-Datei in Xcode 6 , um die PCH-Datei in Ihr Projekt einzufügen.

Updates: Für SWIFT

  1. Erstellen Sie eine neue Swift-Datei [leer ohne Klasse], sagen Sie [AppGlobalMemebers]
  2. & Sofort Mitglied deklarieren / definieren

    Beispiel:

    var STATUS_BAR_GREEN : UIColor  = UIColor(red: 106/255.0, green: 161/255.0, blue: 7/255.0, alpha: 1)  //
    1. Wenn Sie das globale App-Mitglied in einer Klassendatei definieren möchten, z. B. Appdelegate- oder Singleton-Klasse oder eine andere, deklarieren Sie das angegebene Mitglied über der Klassendefinition
Yogesh Lolusare
quelle
2

Globale Deklarationen sind interessant, aber für mich hat sich meine Art zu codieren grundlegend geändert, globale Instanzen von Klassen zu haben. Ich habe ein paar Tage gebraucht, um wirklich zu verstehen, wie man damit arbeitet, also habe ich es hier schnell zusammengefasst

Ich verwende globale Instanzen von Klassen (1 oder 2 pro Projekt, falls erforderlich), um den Kerndatenzugriff oder einige Handelslogiken neu zu gruppieren.

Wenn Sie beispielsweise ein zentrales Objekt haben möchten, das alle Restauranttabellen verwaltet, erstellen Sie Ihr Objekt beim Start, und das ist es. Dieses Objekt kann Datenbankzugriffe verarbeiten ODER im Speicher verarbeiten, wenn Sie es nicht speichern müssen. Es ist zentralisiert, Sie zeigen nur nützliche Schnittstellen ...!

Es ist eine große Hilfe, objektorientiert und eine gute Möglichkeit, alles, was Sie haben, an den gleichen Ort zu bringen

Ein paar Codezeilen:

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

und Objektimplementierung:

@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

für die Verwendung ist es wirklich einfach:

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

Gregoire Mulliez
quelle
3
Der technische Name für dieses Entwurfsmuster ist Singleton. en.wikipedia.org/wiki/Singleton_pattern
Basil Bourque
Es ist keine gute Idee, statische Daten (keine statischen Klassen) im Shared Manager zu behalten.
Onder OZCAN
1

Die akzeptierte Antwort hat 2 Schwächen. Erstens, wie andere betonten, ist die Verwendung, #definederen Debugging schwieriger ist, stattdessen die extern NSString* const kBaseUrlStruktur. Zweitens definiert es eine einzelne Datei für Konstanten. IMO, das ist falsch, weil die meisten Klassen keinen Zugriff auf diese Konstanten benötigen oder auf alle zugreifen können. Außerdem kann die Datei aufgebläht werden, wenn alle Konstanten dort deklariert sind. Eine bessere Lösung wäre, Konstanten in 3 verschiedenen Schichten zu modularisieren:

  1. Systemschicht : SystemConstants.hoder AppConstants.hbeschreibt Konstanten im globalen Bereich, auf die jede Klasse im System zugreifen kann. Deklarieren Sie hier nur die Konstanten, auf die von verschiedenen Klassen zugegriffen werden muss, die nicht miteinander verbunden sind.

  2. ModuleNameConstants.hModul- / Subsystemschicht : , beschreibt eine Reihe von Konstanten, die für eine Reihe verwandter Klassen innerhalb eines Moduls / Subsystems typisch sind.

  3. Klassenebene: Konstanten befinden sich in der Klasse und werden nur von dieser verwendet.

Nur 1,2 beziehen sich auf die Frage.

Stefan
quelle
0

Ein Ansatz, den ich zuvor verwendet habe, besteht darin, eine Datei zu erstellen Settings.plistund sie NSUserDefaultsbeim Start mit zu laden registerDefaults:. Sie können dann wie folgt auf den Inhalt zugreifen:

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

Obwohl ich noch keine Android-Entwicklung durchgeführt habe, klingt es so, als ob dies analog zu der von Ihnen beschriebenen String-Ressourcendatei ist. Der einzige Nachteil ist, dass Sie den Präprozessor nicht zum Wechseln zwischen Einstellungen verwenden können (z DEBUG. B. im Modus). Ich nehme an, Sie könnten jedoch eine andere Datei laden.

NSUserDefaults Dokumentation.

Chris Doble
quelle
9
Ist das nicht ein bisschen übertrieben, wenn Sie nur eine Konstante wollen? Und warum sollten Sie sie in eine potenziell veränderbare Datei einfügen? (Besonders wenn es so kritisch ist wie die IP Ihres Master-Servers, ohne die Ihre App nicht funktioniert).
Cyrille
Ich glaube , dass dieser Ansatz hat mehrere Vorteile, die wichtigste ist , dass Sie Ihre Einstellungen im richtigen Format zurückgegeben werden ( NSString, NSNumberusw.). Sicher, Sie könnten Ihre #defines einpacken , um dasselbe zu tun, aber dann sind sie nicht so einfach zu bearbeiten. Die plistBearbeitungsoberfläche ist auch schön. :) Obwohl ich damit einverstanden bin, dass Sie keine supergeheimen Dinge wie Verschlüsselungsschlüssel hineinstecken sollten, mache ich mir keine allzu großen Sorgen um Benutzer, die an Orten herumstochern, an denen sie nicht sein sollten - wenn sie die App kaputt machen, ist es ihre eigene Schuld .
Chris Doble
1
Klar, ich stimme Ihren Argumenten zu. Wie Sie sagen, verpacke ich mein #defines, um den richtigen Typ zurückzugeben, aber ich bin es gewohnt, solche Konstantendateien zu bearbeiten, da ich immer gelernt habe, globale Konstanten wie diese in eine separate Konstantendatei zu schreiben, seit ich Pascal gelernt habe auf einem alten 286 :) Und was den Benutzer betrifft, der überall herumstochert, stimme ich auch zu, es ist ihre Schuld. Es ist wirklich nur eine Frage des Geschmacks.
Cyrille
@ Chris Doble: Nein, Ressourcendateien in Android ähneln nicht NSUserDefaults. SharedPreferences und Preferences sind das Android-Äquivalent zu NSUserDefaults (obwohl leistungsfähiger als NSUserDefaults). Ressourcen in Android sollen Logik von Inhalten trennen, was die Lokalisierung und viele andere Zwecke betrifft.
mrd
0

Für eine Nummer können Sie es wie verwenden

#define MAX_HEIGHT 12.5
Gihan
quelle