I'm developing a Cocoa application, and I'm using constant NSString
s as ways to store key names for my preferences.
I understand this is a good idea because it allows easy changing of keys if necessary.
Plus, it's the whole 'separate your data from your logic' notion.
Anyway, is there a good way to make these constants defined once for the whole application?
I'm sure that there's an easy and intelligent way, but right now my classes just redefine the ones they use.
This question is related to
ios
objective-c
cocoa
nsstring
constants
Try using a class method:
+(NSString*)theMainTitle
{
return @"Hello World";
}
I use it sometimes.
Easiest way:
// Prefs.h
#define PREFS_MY_CONSTANT @"prefs_my_constant"
Better way:
// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;
// Prefs.m
NSString * const PREFS_MY_CONSTANT = @"prefs_my_constant";
One benefit of the second is that changing the value of a constant does not cause a rebuild of your entire program.
I use a singleton class, so that I can mock the class and change the constants if necessary for testing. The constants class looks like this:
#import <Foundation/Foundation.h>
@interface iCode_Framework : NSObject
@property (readonly, nonatomic) unsigned int iBufCapacity;
@property (readonly, nonatomic) unsigned int iPort;
@property (readonly, nonatomic) NSString * urlStr;
@end
#import "iCode_Framework.h"
static iCode_Framework * instance;
@implementation iCode_Framework
@dynamic iBufCapacity;
@dynamic iPort;
@dynamic urlStr;
- (unsigned int)iBufCapacity
{
return 1024u;
};
- (unsigned int)iPort
{
return 1978u;
};
- (NSString *)urlStr
{
return @"localhost";
};
+ (void)initialize
{
if (!instance) {
instance = [[super allocWithZone:NULL] init];
}
}
+ (id)allocWithZone:(NSZone * const)notUsed
{
return instance;
}
@end
And it is used like this (note the use of a shorthand for the constants c - it saves typing [[Constants alloc] init]
every time):
#import "iCode_FrameworkTests.h"
#import "iCode_Framework.h"
static iCode_Framework * c; // Shorthand
@implementation iCode_FrameworkTests
+ (void)initialize
{
c = [[iCode_Framework alloc] init]; // Used like normal class; easy to mock!
}
- (void)testSingleton
{
STAssertNotNil(c, nil);
STAssertEqualObjects(c, [iCode_Framework alloc], nil);
STAssertEquals(c.iBufCapacity, 1024u, nil);
}
@end
As Abizer said, you could put it into the PCH file. Another way that isn't so dirty is to make a include file for all of your keys and then either include that in the file you're using the keys in, or, include it in the PCH. With them in their own include file, that at least gives you one place to look for and define all of these constants.
// Prefs.h
extern NSString * const RAHUL;
// Prefs.m
NSString * const RAHUL = @"rahul";
If you want something like global constants; a quick an dirty way is to put the constant declarations into the pch
file.
There is also one thing to mention. If you need a non global constant, you should use static
keyword.
Example
// In your *.m file
static NSString * const kNSStringConst = @"const value";
Because of the static
keyword, this const is not visible outside of the file.
Minor correction by @QuinnTaylor: static variables are visible within a compilation unit. Usually, this is a single .m file (as in this example), but it can bite you if you declare it in a header which is included elsewhere, since you'll get linker errors after compilation
If you like namespace constant, you can leverage struct, Friday Q&A 2011-08-19: Namespaced Constants and Functions
// in the header
extern const struct MANotifyingArrayNotificationsStruct
{
NSString *didAddObject;
NSString *didChangeObject;
NSString *didRemoveObject;
} MANotifyingArrayNotifications;
// in the implementation
const struct MANotifyingArrayNotificationsStruct MANotifyingArrayNotifications = {
.didAddObject = @"didAddObject",
.didChangeObject = @"didChangeObject",
.didRemoveObject = @"didRemoveObject"
};
The accepted (and correct) answer says that "you can include this [Constants.h] file... in the pre-compiled header for the project."
As a novice, I had difficulty doing this without further explanation -- here's how: In your YourAppNameHere-Prefix.pch file (this is the default name for the precompiled header in Xcode), import your Constants.h inside the #ifdef __OBJC__
block.
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "Constants.h"
#endif
Also note that the Constants.h and Constants.m files should contain absolutely nothing else in them except what is described in the accepted answer. (No interface or implementation).
I myself have a header dedicated to declaring constant NSStrings used for preferences like so:
extern NSString * const PPRememberMusicList;
extern NSString * const PPLoadMusicAtListLoad;
extern NSString * const PPAfterPlayingMusic;
extern NSString * const PPGotoStartupAfterPlaying;
Then declaring them in the accompanying .m file:
NSString * const PPRememberMusicList = @"Remember Music List";
NSString * const PPLoadMusicAtListLoad = @"Load music when loading list";
NSString * const PPAfterPlayingMusic = @"After playing music";
NSString * const PPGotoStartupAfterPlaying = @"Go to startup pos. after playing";
This approach has served me well.
Edit: Note that this works best if the strings are used in multiple files. If only one file uses it, you can just do #define kNSStringConstant @"Constant NSString"
in the .m file that uses the string.
If you want to call something like this NSString.newLine;
from objective c, and you want it to be static constant, you can create something like this in swift:
public extension NSString {
@objc public static let newLine = "\n"
}
And you have nice readable constant definition, and available from within a type of your choice while stile bounded to context of type.
I am generally using the way posted by Barry Wark and Rahul Gupta.
Although, I do not like repeating the same words in both .h and .m file. Note, that in the following example the line is almost identical in both files:
// file.h
extern NSString* const MyConst;
//file.m
NSString* const MyConst = @"Lorem ipsum";
Therefore, what I like to do is to use some C preprocessor machinery. Let me explain through the example.
I have a header file which defines the macro STR_CONST(name, value)
:
// StringConsts.h
#ifdef SYNTHESIZE_CONSTS
# define STR_CONST(name, value) NSString* const name = @ value
#else
# define STR_CONST(name, value) extern NSString* const name
#endif
The in my .h/.m pair where I want to define the constant I do the following:
// myfile.h
#import <StringConsts.h>
STR_CONST(MyConst, "Lorem Ipsum");
STR_CONST(MyOtherConst, "Hello world");
// myfile.m
#define SYNTHESIZE_CONSTS
#import "myfile.h"
et voila, I have all the information about the constants in .h file only.
A slight modification of the suggestion of @Krizz, so that it works properly if the constants header file is to be included in the PCH, which is rather normal. Since the original is imported into the PCH, it won't reload it into the .m
file and thus you get no symbols and the linker is unhappy.
However, the following modification allows it to work. It's a bit convoluted, but it works.
You'll need 3 files, .h
file which has the constant definitions, the .h
file and the .m
file, I'll use ConstantList.h
, Constants.h
and Constants.m
, respectively. the contents of Constants.h
are simply:
// Constants.h
#define STR_CONST(name, value) extern NSString* const name
#include "ConstantList.h"
and the Constants.m
file looks like:
// Constants.m
#ifdef STR_CONST
#undef STR_CONST
#endif
#define STR_CONST(name, value) NSString* const name = @ value
#include "ConstantList.h"
Finally, the ConstantList.h
file has the actual declarations in it and that is all:
// ConstantList.h
STR_CONST(kMyConstant, "Value");
…
A couple of things to note:
I had to redefine the macro in the .m
file after #undef
ing it for the macro to be used.
I also had to use #include
instead of #import
for this to work properly and avoid the compiler seeing the previously precompiled values.
This will require a recompile of your PCH (and probably the entire project) whenever any values are changed, which is not the case if they are separated (and duplicated) as normal.
Hope that is helpful for someone.
Source: Stackoverflow.com