Де зберігати глобальні константи в додатку iOS?


111

Більшість моделей мого додатка iOS запитують веб-сервер. Я хотів би мати файл конфігурації, який зберігає базову URL-адресу сервера. Це буде виглядати приблизно так:

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

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

Коментуючи ту чи іншу лінію, я можу моментально змінити, на який сервер вказують мої моделі. Моє запитання: яка найкраща практика зберігання глобальних констант в iOS? В програмуванні Android у нас є цей вбудований файл ресурсних рядків . У будь-якій діяльності (еквіваленті UIViewController ) ми можемо отримати ці рядкові константи за допомогою:

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

Мені було цікаво, чи є в iOS SDK аналогічне місце для зберігання констант. Якщо ні, то яка найкраща практика в Objective-C для цього?

Відповіді:


145

Ви також можете зробити

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

скажімо, у файлі заголовка "константи" constants.h. Тоді робіть

#include "constants.h"

вгорі кожного файлу, де вам потрібна ця константа.

Таким чином, ви можете перемикатися між серверами залежно від прапорів компілятора, як у:

#ifdef DEBUG
    #define kBaseURL @"http://192.168.0.123/"
#else
    #define kBaseURL @"http://myproductionserver.com/"
#endif

Я використовую "constants.h"підхід, декларуючи staticзмінні на основі #ifdef VIEW_CONSTANTS ... #endif. Таким чином, у мене є один константний файл для всіх додатків, але кожен з інших моїх кодових файлів має #defineрізні набори констант, які слід включити до #includeвведення файлу констант (зупиняє всі попередження компілятора "визначені, але не використовувані").

2
З цим рішенням я зіткнувся з двома питаннями. По-перше, коли я використовував #decalare, у мене з’явилася помилка компіляції, яка сказала « невірна директива попередньої обробки заявити » Тому я замінив це #defineзамість цього. Інша проблема - використання константи. Я хотів створити іншу константу з static NSString* const fullUrl = [NSString stringWithFormat:@"%@%@", kbaseUrl, @"script.php"], але, мабуть, незаконно створювати конкурси з виразом. Я отримую помилку " елемент ініціалізатора не є постійним ".
JoJo

1
@Cyrille Android дуже цікаво практикувати, є деякі можливості, які ви не могли собі уявити на iOS! Все одно дякую за відповідь
klefevre

8
Віддайте перевагу const на #define, де це можливо - ви отримаєте кращу перевірку часу компіляції, і налагодження працює краще.
окулус

2
@AnsonYao зазвичай, коли це трапляється зі мною, я забув видалити крапку з крапки з #define kBaseURL @"http://192.168.0.123/";
кодом

168

Ну, ви хочете, щоб декларація була локальною для інтерфейсів, до яких це стосується - файл констант, що стосується програми, - це не дуже добре.

Крім того, бажано просто оголосити extern NSString* constсимвол, а не використовувати #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

Крім опущення C ++ сумісної декларації Extern, це те, що ви, як правило, бачите, використовуване в рамках Obj-C Apple від Apple.

Якщо константа повинна бути видно лише одному файлу чи функції, то static NSString* const baseUrlу вашому *.mце добре.


26
Не впевнений, чому прийнята відповідь має 40 голосів за відстоювання #define - const справді кращий.
окулус

1
Безумовно, що const NSString краще, ніж #define, це має бути прийнятою відповіддю. #define створює новий рядок щоразу, коли використовується визначене значення.
jbat100

1
@ jbat100 Я не думаю, що це створює нову рядок. Я думаю, що компілятор виявить, чи ваш код створює ту саму статичну рядок 300 000 разів і створить її лише один раз. @"foo"не те саме, що [[NSString alloc] initWithCString:"foo"].
Abhi Beckert

@AbhiBeckert Я думаю, що пункт jbat намагався зробити, це те, що при #defineвикористанні дублікатів вашої константи можливо (тобто рівність вказівника може вийти з ладу) - не те, що дослівний вираз NSString створює тимчасовий раз, коли виконується.
Justin

1
Я погоджуюся, що #define - це погана ідея, я просто хотів виправити помилку, яку він зробив, що це створить кілька об'єктів. Крім того, рівність вказівника не може покладатися навіть на константи. Він може бути завантажений з NSUserDefaults або щось подібне. Завжди використовуйте isEqual:.
Abhi Beckert

39

Спосіб визначення глобальних констант:


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

Потім у вашому {$ APP} -Prefix.pch файлі:

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

Якщо у вас виникли якісь проблеми, спочатку переконайтесь, що для параметра заголовка префіксального префіксу встановлено значення НІ.


5

Ви також можете об'єднати рядкові константи на зразок цього:

  #define kBaseURL @"http://myServer.com"
  #define kFullURL kBaseURL @"/api/request"

4

Я думаю, що інший спосіб зробити це набагато простіше, і ви просто включите його у потрібні вам файли, а не ВСІ файли, як у файлі з префіксом .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 */

Після цього ви включаєте цей заголовок у потрібний файл заголовка. Ви включаєте його у файл заголовка для конкретного класу, який ви хочете, щоб він включав:

#include "Constants.h"

У моєму тестуванні статичні константи const не застосовуються у відладчику (lldb Xcode). "error: use of undeclared identifier .."
jk7

3
  1. Я визначаю глобальну константу у файлі YOURPROJECT-Prefix.pch.
  2. #define BASEURl @"http://myWebService.appspot.com/xyz/xx"
  3. то в будь-якому місці проекту використовувати BASEURL:

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

Оновлено: У Xcode 6 ви не знайдете .pch-файлу за замовчуванням, створеного у вашому проекті. Тому, будь ласка, використовуйте файл PCH у Xcode 6, щоб вставити .pch файл у свій проект.

Оновлення: для SWIFT

  1. Створіть новий файл Swift [порожній без класу] скажіть [AppGlobalMemebers]
  2. & Відразу оголосити / визначити члена

    Приклад:

    var STATUS_BAR_GREEN : UIColor  = UIColor(red: 106/255.0, green: 161/255.0, blue: 7/255.0, alpha: 1)  //
    1. Якщо ви хочете визначити глобального члена програми в будь-якому файлі класу, скажімо, Appdelegate або Singleton class або будь-який, оголосити даний член вище визначення класу

2

Глобальні декларації цікаві, але, на мою думку, те, що глибоко змінило мій спосіб кодування, - це мати глобальні екземпляри занять. На це мені знадобилося кілька днів, щоб зрозуміти, як з цим працювати, тому я швидко її підсумував тут

Я використовую глобальні екземпляри класів (1 або 2 на проект, якщо потрібно), щоб перегрупувати доступ до основних даних або деякі логіки торгів.

Наприклад, якщо ви хочете мати центральний об'єкт, який обробляє всі таблиці ресторанів, ви створюєте об'єкт при запуску, і це все. Цей об'єкт може обробляти доступ до бази даних АБО обробляти його в пам'яті, якщо вам не потрібно зберігати його. Це централізовано, ви показуєте лише корисні інтерфейси ...!

Це чудова допомога, орієнтована на об’єкти і хороший спосіб отримати все, що ви шукаєте, там же

Кілька рядків коду:

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

та реалізація об'єкта:

@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

для його використання це дуже просто:

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


3
Технічна назва цього шаблону дизайну - Singleton. en.wikipedia.org/wiki/Singleton_pattern
Василь Бурк

Зберігати статичні дані (не статичні класи) у спільному менеджері - це не дуже гарна ідея.
Onder OZCAN

1

Прийнята відповідь має 2 слабкі сторони. По-перше, як наголошували інші, використання #defineяких важче налагоджувати, використовуйте натомість extern NSString* const kBaseUrlструктуру. По-друге, він визначає єдиний файл для констант. ІМО, це неправильно, оскільки більшість класів не потребує доступу до цих констант або для доступу до них плюс файл може стати роздутим, якщо всі константи оголошені там. Кращим рішенням буде модуляція констант на 3 різних шарах:

  1. Системний рівень: SystemConstants.hабо AppConstants.h який описує константи в глобальному масштабі, до яких можна отримати доступ будь-якого класу в системі. Декларуйте тут лише ті константи, до яких потрібно отримати доступ з різних класів, які не пов'язані між собою.

  2. Модульний / підсистемний рівень:, ModuleNameConstants.hописує набір констант, характерних для набору відповідних класів, всередині модуля / підсистеми.

  3. Класовий шар: Константи знаходяться в класі і використовуються лише ним.

Лише 1,2 стосуються питання.


0

Я використовував підхід, щоб створити файл Settings.plistі завантажити його в момент NSUserDefaultsзапуску registerDefaults:. Потім ви можете отримати доступ до його вмісту за допомогою наступного:

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

Хоча я ще не займався розробкою Android, це здається, що це аналогічно файлу ресурсів рядків, який ви описали. Єдиним недоліком є ​​те, що ви не можете використовувати препроцесор для обміну налаштуваннями (наприклад, в DEBUGрежимі). Я думаю, ви можете завантажити інший файл.

NSUserDefaults документація.


9
Хіба це не трохи надмірності, коли все, що ви хочете, є постійною? А також, навіщо ставити його в файл, який може змінюватися? (Особливо, коли це щось таке критичне, як IP вашого головного сервера, без якого ваша програма не працює).
Кирила

Я вважаю , що такий підхід має ряд переваг, найзначніше істота , що ваші настройки повертаються в правильному форматі ( NSString, NSNumberі т.д.). Звичайно, ви можете обернути свої #defineфайли, щоб зробити те саме, але тоді їх не так просто редагувати. Інтерфейс plistредагування теж приємний. :) Хоча я погоджуюся, що вам не слід вкладати сюди секретні речі, такі як ключі шифрування, я не надто переймаюся користувачами, котрі розігруються в місцях, де вони не повинні бути - якщо вони зламають додаток, це сама їх вина .
Кріс Добл

1
Звичайно, я згоден з вашими аргументами. Як ви кажете, я обертаю свій #defines, щоб повернути правильний тип, але я звик редагувати файли таких констант, оскільки я завжди вчився ставити такі глобальні константи в окремий файл констант, ще з тих днів, коли я дізнався Паскаль на старий 286 :) А що стосується користувача, який скрізь тикає, я теж погоджуюся, це їх вина. Це справді справа смаку.
Кирила

@Chris Doble: Ні, файли ресурсів в Android не схожі на NSUserDefaults. SharedPreferences і Preferences - це Android, еквівалентний NSUserDefaults (хоча і потужніший, ніж NSUserDefaults). Ресурси в Android мають на меті відокремити логіку від контенту, як для локалізації, так і для багатьох інших цілей.
MRD

0

Для ряду ви можете використовувати його так

#define MAX_HEIGHT 12.5

0

Я б використовував об'єкт конфігурації, який ініціалізується з plist. Навіщо турбувати інші заняття нерелевантними зовнішніми предметами?

Я створив eppz!settignsсолі з цієї причини. Див. Статтю Розширений, але простий спосіб збереження до NSUserDefaults для включення значень за замовчуванням з plist.

введіть тут опис зображення


Правда. Wordpress якось не вирішує ...: / ... все одно переходьте за цим посиланням blog.eppz.eu/?p=926 : D
Гері Борбас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.