111

Most of the models in my iOS app query a web server. I would like to have a configuration file storing the base URL of the server. It will look something like this:

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

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

By commenting out one line or the other, I can instantly change which server my models point to. My question is, what's the best practice for storing global constants in iOS? In Android programming, we have this built-in strings resource file. In any Activity (the equivalent of a UIViewController), we can retrieve those string constants with:

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

I was wondering if the iOS SDK has an analogous place to store constants. If not, what is the best practice in Objective-C to do so?

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
JoJo
  • 19,587
  • 34
  • 106
  • 162

11 Answers11

169

Well, you want the declaration local to the interfaces it relates to -- the app-wide constants file is not a good thing.

As well, it's preferable to simply declare an extern NSString* const symbol, rather than use a #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.example/";
#endif

Apart from the omission of the C++ compatible Extern declaration, this is what you will generally see used in Apple's Obj-C frameworks.

If the constant needs to be visible to just one file or function, then static NSString* const baseUrl in your *.m is good.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
justin
  • 104,054
  • 14
  • 179
  • 226
  • 26
    Not sure why the accepted answer has 40 votes for advocating #define -- const is indeed better. – occulus Mar 23 '13 at 15:55
  • 1
    Definitely const NSString is better than #define, this should be the accepted answer. #define creates a new string everytime the defined value is used. – jbat100 Jul 19 '13 at 12:32
  • 1
    @jbat100 I don't think it creates a new string. I think the compiler detects if your code creates the same static string 300,000 times and will only create it once. `@"foo"` is not the same as `[[NSString alloc] initWithCString:"foo"]`. – Abhi Beckert Jul 19 '13 at 14:17
  • @AbhiBeckert i think the point jbat was trying to make is that it's possible to end up with duplicates of your constant when `#define` is used (i.e. pointer equality could fail) - not that an NSString literal expression produces a temporary whenever executed. – justin Jul 19 '13 at 14:38
  • 1
    I agree #define is a bad idea, I just wanted to correct the error he made that it will create multiple objects. Also, pointer equality can't be relied on even for constants. It might be loaded from NSUserDefaults or something. Always use isEqual:. – Abhi Beckert Jul 19 '13 at 14:43
  • @AbhiBeckert technically, it can result in multiple instances. you probably don't see that in iOS programs very often (where such definitions would be in an image which is linked statically) because duplicate string constants in an image can be removed (not just C strings, but NS/CF strings too). so your literals get put into the image's __TEXT.__cstring.cstring_literals section, and CFStrings into __DATA.__cfstring. so (as you say) it is pretty reasonable to expect duplicate instances created by nscfstring literal expressions to be the same instance across your app (but not a guarantee). – justin Jul 19 '13 at 16:06
  • @AbhiBeckert but if, say, notification names were declared using `#define` in a library header, the constants (produced by textual substitution of the define) were used by each image, and the library was loaded dynamically, then each image (e.g. your app and the dynamic library/framework) would have separate instances of that `#defined` 'constant' because each image would have separate data sections which they reference. again, a lot's been done to reduce redundant instances, as you say. – justin Jul 19 '13 at 16:42
145

You could also do a

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

in a "constants" header file, say constants.h. Then do

#include "constants.h"

at the top of every file where you need this constant.

This way, you can switch between servers depending on compiler flags, as in:

#ifdef DEBUG
    #define kBaseURL @"http://192.168.0.123/"
#else
    #define kBaseURL @"http://myproductionserver.example/"
#endif
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Cyrille
  • 25,014
  • 12
  • 67
  • 90
  • I use the `"constants.h"` approach, declaring `static` variables based on `#ifdef VIEW_CONSTANTS ... #endif`. So I have one application-wide constants file, but each of my other code files `#define`s different sets of constants to include before `#include`-ing the constants file (stops all those "defined but not used" compiler warnings). –  Aug 19 '11 at 02:26
  • 2
    There's two issues I ran into with this solution. First, when I used `#decalare`, I got a compile error saying "*invalid preprocessing directive declare*". So I changed it to `#define` instead. The other problem is using the constant. I wanted to create another constant with `static NSString* const fullUrl = [NSString stringWithFormat:@"%@%@", kbaseUrl, @"script.php"]`, but apparently it's illegal to create consts with an expression. I get the error "*initializer element is not constant*". – JoJo Aug 19 '11 at 18:34
  • Indeed, it's `#define`, not `#declare`. My mistake, I'm correcting my answer. However it's strange that you get this error. It has to do with the `static NSString* const fullURL` part, not the use of the constant (it's a preprocessor macro, so it's just as if you wrote `...@"%@%@", @"something", @"script.php"`). BTW instead of using a `stringWithFormat` you could just have written `[kBaseUrl stringByAppendingString:@"script.php"]`, which takes less time (no need to parse the format string). – Cyrille Aug 20 '11 at 16:25
  • I was told by a uber nerd from my company that one cannot create constant objects from an expression of other objects. In this case, we cannot create the constant `NSString fullUrl` from the other constant `NSString baseUrl`. This is because objects by nature are created at runtime. Instead, the uber nerd told me to just store my constant strings as `char*` to avoid this compile error. `char*` can be easily converted to NSString's when needed by the multitude of iOS API calls that need NSStrings. This is my final solution. – JoJo Aug 20 '11 at 18:27
  • @JoJo how can you concatenate the strings with char *? Can you show some code please? – Fred Collins Apr 07 '12 at 13:42
  • I know this is an old answer, but would including the constants.h file in the apps .pch file work as well? For the constants to truly be global? – Piotr Tomasik Oct 12 '12 at 09:17
  • I somehow don't have to import the header file, I can use the constants just fine without referencing them. – Marius Soutier Nov 12 '12 at 10:32
  • 1
    @Cyrille Android is really interesting to practice, there is some possibilities you could not imagine on iOS ! Thanks anyway for reply – klefevre Feb 26 '13 at 16:59
  • @kl94 I hereby refuse to troll upon Java on Stack Overflow ;) – Cyrille Feb 26 '13 at 18:41
  • @Cyrille I think I could troll upon Java a lot too ^^ I was talking about Android and its possibilities. Not Java and its ugliness ! (Oops troll :D). have a good day ! – klefevre Feb 28 '13 at 07:42
  • 8
    Prefer const over #define where possible -- you get better compile-time checking, and debugging works better. – occulus Mar 23 '13 at 15:54
  • how to access the defined variable? I always get an error saying expected expression when I use the variable. like NSString *str=kBaseUrl or NSString *str=@kBaseUrl. Neither works in my case...I must have missed something... – Anson Yao Oct 30 '14 at 04:36
  • 2
    @AnsonYao usually when that happens to me I forgot to remove a semicolon from the #define, such as `#define kBaseURL @"http://192.168.0.123/";` – Gyfis Apr 11 '15 at 22:13
39

The way I define global constants:


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.example/";
#endif

Then in your {$APP}-Prefix.pch file:

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

If you experience any problems, first make sure that you have the Precompile Prefix Header option set to NO.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Piotr Tomasik
  • 9,074
  • 4
  • 44
  • 57
5

You can also concatenate string constants like this:

  #define kBaseURL @"http://myServer.example"
  #define kFullURL kBaseURL @"/api/request"
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Martin Reichl
  • 932
  • 9
  • 13
4

I do think that another way to do this is far simpler and you will just include it in files you need them included in, not ALL the files, like with the .pch prefix file:

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

After that you include this header file in the header file that you want. You include it in header file for the specific class that you want it included in:

#include "Constants.h"
Vladimir Despotovic
  • 3,200
  • 2
  • 30
  • 58
  • In my testing, static const constants are not usable in the debugger (Xcode's lldb). `"error: use of undeclared identifier .."` – jk7 Apr 04 '20 at 02:18
3
  1. I define global constant in YOURPROJECT-Prefix.pch file.
  2. #define BASEURl @"http://myWebService.appspot.com/xyz/xx"
  3. then anywhere in project to use BASEURL:

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

Updated: In Xcode 6 you will not find default .pch file created in your project. So please use PCH File in Xcode 6 to insert .pch file in your project.

Updates: For SWIFT

  1. Create new Swift file [empty without class] say [AppGlobalMemebers]
  2. & Right away declare / define member

    Example:

    var STATUS_BAR_GREEN : UIColor  = UIColor(red: 106/255.0, green: 161/255.0, blue: 7/255.0, alpha: 1)  //
    
    1. If you want to define the app global member in any class file say Appdelegate or Singleton class or any, declare given member above class definition
Community
  • 1
  • 1
Yogesh Lolusare
  • 2,162
  • 1
  • 24
  • 35
2

Global declarations are interesting but, for me, what changed deeply my way to code was to have global instances of classes. It took me a couple of day's to really understand how to work with it so I quickly summarized it here

I use global instances of classes (1 or 2 per project, if needed), to regroup core data access, or some trades logics.

For instance if you want to have a central object handling all restaurant tables you create you object at startup and that is it. This object can handle database accesses OR handle it in memory if you don't need to save it. It's centralized, you show only useful interfaces ... !

It's a great help, object oriented and a good way to get all you stuff at the same place

A few lines of code :

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

and object implementation :

@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

for using it it's really simple :

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

Koen.
  • 25,449
  • 7
  • 83
  • 78
Gregoire Mulliez
  • 1,132
  • 12
  • 20
1

The accepted answer has 2 weaknesses. First, as others pointed is usage of #define which is harder to debug, use instead extern NSString* const kBaseUrl structure. Second, it defines a single file for constants. IMO, this is wrong because most of classes don't need access to those constants or to access all of them plus file can become bloated if all constants are declared there. A better solution would be to modularize constants at 3 different layers:

  1. System layer: SystemConstants.h or AppConstants.h which describes constants at global scope, which can be accessed by any class in the system. Declare here only those constants that must be accessed from different classes that are not related.

  2. Module/Sub-system layer: ModuleNameConstants.h, describes a set of constants which are typical for a set of related classes, inside of a module/sub-system.

  3. Class layer: Constants resides in the class and are used only by it.

Only 1,2 are related to the question.

nsinvocation
  • 7,559
  • 3
  • 41
  • 46
0

For a number you can use it like

#define MAX_HEIGHT 12.5
Gihan
  • 2,476
  • 2
  • 27
  • 33
0

I'd use a configuration object that initializes from a plist. Why bother other classes with irrelevant external stuff?

I created eppz!settigns soley for this reason. See article Advanced yet simple way to save to NSUserDefaults for incorporating default values from a plist.

enter image description here

Geri Borbás
  • 15,810
  • 18
  • 109
  • 172
0

An approach I've used before is to create a file Settings.plist and load it into NSUserDefaults upon launch using registerDefaults:. You can then access its contents with the following:

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

While I haven't done any Android development, it sounds as though this is analogous to the strings resource file you described. The only downside is that you can't use the preprocessor to swap between settings (e.g. in DEBUG mode). I suppose you could load in a different file, though.

NSUserDefaults documentation.

  • 9
    Isn't that a bit overkill when all you want is a constant?And also, why put it in a potentially modifiable file? (Especially when it's something as critical as the IP of your master server, without which your app doesn't work). – Cyrille Aug 19 '11 at 02:30
  • I feel that this approach has several benefits, the most significant being that your settings are returned in the correct format (`NSString`, `NSNumber`, etc.). Sure, you could wrap your `#define`s to do the same thing, but then they're not as easy to edit. The `plist` editing interface is nice, too. :) While I agree that you shouldn't put super secret stuff like encryption keys in there, I'm not too concerned about users who are poking around in places they shouldn't be—if they break the app, it's their own fault. –  Aug 19 '11 at 02:55
  • 1
    Sure, I agree with your arguments. As you say, I wrap my `#define`s to return the correct type, but I'm accustomed to editing such constants files, as I've always learned to put global constants like this in a separate constants file, back from the days I learnt Pascal on an old 286 :) And as to the user who pokes around everywhere, I agree too, it's their fault. It's just a matter of taste, really. – Cyrille Aug 19 '11 at 03:00
  • @Chris Doble: No, resource files in Android are not similiar to NSUserDefaults. SharedPreferences and Preferences are the Android equivalent to NSUserDefaults (though more powerful than NSUserDefaults). Resources in Android are intented to separate logic from content, as for localisation, and for many other uses. – mrd Jul 23 '14 at 14:04