Gdzie przechowywać stałe globalne w aplikacji iOS?

111

Większość modeli w mojej aplikacji na iOS odpytuje serwer WWW. Chciałbym mieć plik konfiguracyjny przechowujący podstawowy adres URL serwera. Będzie wyglądać mniej więcej tak:

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

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

Komentując jedną lub drugą linię, mogę natychmiast zmienić serwer, na który wskazują moje modele. Moje pytanie brzmi: jaka jest najlepsza praktyka przechowywania globalnych stałych w iOS? W programowaniu na Androida mamy ten wbudowany plik zasobów ciągów . W dowolnym działaniu (odpowiedniku UIViewController ) możemy pobrać te stałe łańcuchowe za pomocą:

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

Zastanawiałem się, czy iOS SDK ma analogiczne miejsce do przechowywania stałych. Jeśli nie, jaka jest najlepsza praktyka w Objective-C, aby to zrobić?

JoJo
źródło

Odpowiedzi:

145

Możesz także zrobić

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

powiedzmy w pliku nagłówkowym „stałych” constants.h. Następnie zrób

#include "constants.h"

u góry każdego pliku, w którym potrzebujesz tej stałej.

W ten sposób możesz przełączać się między serwerami w zależności od flag kompilatora, jak w:

#ifdef DEBUG
    #define kBaseURL @"http://192.168.0.123/"
#else
    #define kBaseURL @"http://myproductionserver.com/"
#endif
Cyrille
źródło
Stosuję "constants.h"podejście, deklarując staticzmienne na podstawie #ifdef VIEW_CONSTANTS ... #endif. Mam więc jeden plik stałych dla całej aplikacji, ale każdy z moich pozostałych plików z kodem #defineto różne zestawy stałych, które mają zostać #includedołączone przed plikiem stałych (zatrzymuje wszystkie te „zdefiniowane, ale nie używane” ostrzeżenia kompilatora).
2
Z tym rozwiązaniem napotkałem dwa problemy. Po pierwsze, kiedy użyłem #decalare, otrzymałem błąd kompilacji z informacją „ Nieprawidłowa deklaracja dyrektywy preprocessingu ”. Więc zmieniłem to na #define. Innym problemem jest użycie stałej. Chciałem stworzyć inną stałą z static NSString* const fullUrl = [NSString stringWithFormat:@"%@%@", kbaseUrl, @"script.php"], ale najwyraźniej tworzenie stałych z wyrażeniem jest nielegalne. Pojawia się błąd „ Element inicjujący nie jest stały ”.
JoJo
1
@Cyrille Android jest naprawdę interesujący do ćwiczenia, jest kilka możliwości, których nie możesz sobie wyobrazić na iOS! W każdym razie dzięki za odpowiedź
klefevre
8
Jeśli to możliwe, preferuj const zamiast #define - uzyskujesz lepsze sprawdzanie podczas kompilacji, a debugowanie działa lepiej.
occulus
2
@AnsonYao zwykle kiedy mi się to przytrafia, zapomniałem usunąć średnik z #define, na przykład #define kBaseURL @"http://192.168.0.123/";
Gyfis
168

Cóż, chcesz, aby deklaracja była lokalna dla interfejsów, do których się odnosi - plik stałych całej aplikacji nie jest dobrą rzeczą.

Poza tym lepiej jest po prostu zadeklarować extern NSString* constsymbol, zamiast używać #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

Oprócz pominięcia deklaracji Extern zgodnej z C ++, to jest to, co zwykle jest używane w frameworkach Obj-C firmy Apple.

Jeśli stała ma być widoczna tylko dla jednego pliku lub funkcji, to static NSString* const baseUrlw twoim *.mjest dobra.

Justin
źródło
26
Nie jestem pewien, dlaczego zaakceptowana odpowiedź ma 40 głosów za poparciem #define - const jest rzeczywiście lepsze.
occulus
1
Zdecydowanie const NSString jest lepsze niż #define, powinna to być akceptowana odpowiedź. #define tworzy nowy ciąg za każdym razem, gdy używana jest zdefiniowana wartość.
jbat100
1
@ jbat100 Nie sądzę, że tworzy nowy ciąg. Myślę, że kompilator wykrywa, czy kod tworzy ten sam statyczny ciąg 300 000 razy i utworzy go tylko raz. @"foo"to nie to samo co [[NSString alloc] initWithCString:"foo"].
Abhi Beckert,
@AbhiBeckert Myślę, że celem jbat było to, że można skończyć z duplikatami swojej stałej, gdy #definejest używana (tj. Równość wskaźnika może się nie udać) - nie znaczy to, że dosłowne wyrażenie NSString tworzy tymczasowe wyrażenie za każdym razem, gdy jest wykonywane.
justin,
1
Zgadzam się, że #define to zły pomysł, chciałem tylko poprawić błąd, który popełnił, że utworzy wiele obiektów. Ponadto nie można polegać na równości wskaźnika nawet w przypadku stałych. Może być ładowany z NSUserDefaults lub coś w tym stylu. Zawsze używaj isEqual :.
Abhi Beckert,
39

Sposób definiowania stałych globalnych:


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

Następnie w pliku {$ APP} -Prefix.pch:

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

Jeśli wystąpią jakiekolwiek problemy, najpierw upewnij się, że opcja Precompile Prefix Header jest ustawiona na NO.

Piotr Tomasik
źródło
5

Możesz również łączyć stałe łańcuchowe w następujący sposób:

  #define kBaseURL @"http://myServer.com"
  #define kFullURL kBaseURL @"/api/request"
Martin Reichl
źródło
4

Myślę, że inny sposób jest znacznie prostszy i po prostu umieścisz go w plikach, w których są potrzebne, a nie WSZYSTKICH plikach, jak w przypadku pliku z prefiksem .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 */

Następnie umieść ten plik nagłówkowy w żądanym pliku nagłówkowym. Umieszczasz go w pliku nagłówkowym dla określonej klasy, w której ma być uwzględniony:

#include "Constants.h"
Vladimir Despotovic
źródło
W moich testach stałe statyczne nie są używane w debugerze (lldb Xcode). "error: use of undeclared identifier .."
jk7
3
  1. Globalną stałą definiuję w pliku YOURPROJECT-Prefix.pch.
  2. #define BASEURl @"http://myWebService.appspot.com/xyz/xx"
  3. następnie w dowolnym miejscu projektu, aby używać BASEURL:

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

Zaktualizowano: W Xcode 6 nie znajdziesz domyślnego pliku .pch utworzonego w Twoim projekcie. Dlatego użyj pliku PCH w Xcode 6, aby wstawić plik .pch do projektu.

Aktualizacje: dla SWIFT

  1. Utwórz nowy plik Swift [pusty bez klasy] powiedz [AppGlobalMemebers]
  2. & Od razu zadeklaruj / zdefiniuj członka

    Przykład:

    var STATUS_BAR_GREEN : UIColor  = UIColor(red: 106/255.0, green: 161/255.0, blue: 7/255.0, alpha: 1)  //
    1. Jeśli chcesz zdefiniować globalny element członkowski aplikacji w dowolnym pliku klasy, powiedz Appdelegate lub Singleton lub dowolną, zadeklaruj dany element członkowski powyżej definicji klasy
Yogesh Lolusare
źródło
2

Deklaracje globalne są interesujące, ale dla mnie to, co głęboko się zmieniło w moim sposobie kodowania, to posiadanie globalnych instancji klas. Zajęło mi kilka dni, zanim naprawdę zrozumiałem, jak z nim pracować, więc szybko podsumowałem to tutaj

Używam globalnych instancji klas (1 lub 2 na projekt, jeśli to konieczne), aby przegrupować dostęp do podstawowych danych lub logikę niektórych transakcji.

Na przykład, jeśli chcesz mieć centralny obiekt obsługujący wszystkie stoły restauracyjne, tworzysz obiekt podczas uruchamiania i to wszystko. Ten obiekt może obsługiwać dostęp do bazy danych LUB obsłużyć go w pamięci, jeśli nie musisz go zapisywać. Jest scentralizowany, pokazujesz tylko przydatne interfejsy ...!

To świetna pomoc, zorientowana obiektowo i dobry sposób na zebranie wszystkich rzeczy w tym samym miejscu

Kilka linijek kodu:

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

i realizacja obiektów:

@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

za korzystanie z niego jest to naprawdę proste:

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

Gregoire Mulliez
źródło
3
Techniczna nazwa tego wzorca projektowego to Singleton. en.wikipedia.org/wiki/Singleton_pattern
Basil Bourque
Utrzymywanie danych statycznych (nie klas statycznych) w sharedmanager nie jest dobrym pomysłem.
Onder OZCAN
1

Przyjęta odpowiedź ma 2 słabości. Po pierwsze, jak wskazali inni, użycie #definektórego jest trudniejsze do debugowania, użyj zamiast tego extern NSString* const kBaseUrlstruktury. Po drugie, definiuje pojedynczy plik dla stałych. IMO, to jest złe, ponieważ większość klas nie potrzebuje dostępu do tych stałych lub aby uzyskać dostęp do wszystkich z nich, a także plik może zostać rozdęty, jeśli wszystkie stałe są tam zadeklarowane. Lepszym rozwiązaniem byłoby modularyzacja stałych w 3 różnych warstwach:

  1. Warstwa systemowa: SystemConstants.hlub AppConstants.h która opisuje stałe o zasięgu globalnym, do których dostęp może uzyskać każda klasa w systemie. Zadeklaruj tutaj tylko te stałe, do których należy uzyskać dostęp z różnych klas, które nie są powiązane.

  2. Warstwa modułu / podsystemu: ModuleNameConstants.hopisuje zestaw stałych, które są typowe dla zestawu powiązanych klas, wewnątrz modułu / podsystemu.

  3. Warstwa klasy: Stałe znajdują się w klasie i są używane tylko przez nią.

Tylko 1,2 odnosi się do pytania.

Stefan
źródło
0

Podejście, którego używałem wcześniej, polega na utworzeniu pliku Settings.plisti załadowaniu go NSUserDefaultspo uruchomieniu za pomocą registerDefaults:. Następnie możesz uzyskać dostęp do jego zawartości za pomocą:

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

Chociaż nie robiłem żadnego programu na Androida, wydaje się, że jest to analogiczne do opisanego przez ciebie pliku zasobów ciągów. Jedynym minusem jest to, że nie można używać preprocesora do przełączania się między ustawieniami (np. W DEBUGtrybie). Przypuszczam jednak, że możesz załadować inny plik.

NSUserDefaults dokumentacja.

Chris Doble
źródło
9
Czy nie jest to trochę przesada, kiedy wszystko, czego chcesz, to stała, a także po co umieszczać ją w pliku, który może być modyfikowany? (Zwłaszcza, gdy jest to coś tak krytycznego, jak adres IP twojego serwera głównego, bez którego aplikacja nie działa).
Cyrille
Czuję, że to podejście ma kilka zalet, z których najważniejszym było, że ustawienia są zwracane w odpowiednim formacie ( NSString, NSNumber, itd.). Jasne, możesz owinąć swoje #definepliki, aby zrobić to samo, ale wtedy nie są tak łatwe do edycji. plistInterfejs edycji jest ładne, zbyt. :) Chociaż zgadzam się, że nie powinieneś umieszczać tam super tajnych rzeczy, takich jak klucze szyfrujące, nie martwię się zbytnio o użytkowników, którzy szperają w miejscach, w których nie powinni być - jeśli zepsują aplikację, to ich wina .
Chris Doble,
1
Jasne, zgadzam się z twoimi argumentami. Jak mówisz, zawijam swoje, #defineaby zwrócić właściwy typ, ale jestem przyzwyczajony do edytowania takich plików stałych, ponieważ zawsze nauczyłem się umieszczać globalne stałe w oddzielnym pliku stałych, z czasów, gdy uczyłem się Pascala na starym 286 :) A co do użytkownika, który wszędzie grzebuje, to też się zgadzam, to jego wina. Tak naprawdę to tylko kwestia gustu.
Cyrille
@Chris Doble: Nie, pliki zasobów w systemie Android nie są podobne do NSUserDefaults. SharedPreferences i Preferences są odpowiednikiem NSUserDefaults w systemie Android (choć są bardziej wydajne niż NSUserDefaults). Zasoby w systemie Android mają na celu oddzielenie logiki od zawartości, na przykład w przypadku lokalizacji i wielu innych zastosowań.
mrd
0

Dla numeru możesz go używać jak

#define MAX_HEIGHT 12.5
Gihan
źródło