Using a constant NSString as the key for NSUserDefaults - objective-c

I'm using NSUSerDefaults to store user preferences. I remember reading somewhere that setting the keys as constants is a good idea - and I agree. The following code is what I currently have:
[[NSUserDefaults standardUserDefaults]
setObject:[NSNumber numberWithInt:polygon.numberOfSides]
forKey:#"polygonNumberOfSides"];
I tried changing this to:
#implementation Controller
NSString const *kPolygonNumberOfSides = #"polygonNumberOfSides";
-(void)savePolygonInfo {
[[NSUserDefaults standardUserDefaults]
setObject:[NSNumber numberWithInt:polygon.numberOfSides]
forKey:kPolygonNumberOfSides];
}
While this does work, it produces "warning: passing argument 1 of 'objectForKey:' discards qualifiers from pointer target type". I'm keen to keep my code free from compiler warnings. How can I fix this warning?

You should use:
NSString * const kPolygonNumberOfSides = #"..."; // const pointer
instead of:
NSString const * kPolygonNumberOfSides = #"..."; // pointer to const
The first is a constant pointer to an NSString object, while the second is a pointer to a constant NSString object.
It is a subtle difference. The compiler warning happens because setObject:forKey: is declared as follows:
- (void)setObject:(id)value forKey:(NSString *)defaultName;
It is expecting the defaultName argument to be of type NSString *. When you instead pass in a pointer to a constant, you've given it something different.
Update: I want to point out that these constants should be defined as static if they are only going to be used from within a single file. I say this because I have run across this problem myself: if you do not declare them as static, then they will exist in the global namespace, and you will not be able to use a variable with the same the same name in another file. see Constants in Objective-C for more information. To explain by example, this is what I currently use for keys that I only need to use in one .m file:
static NSString * const kSomeLabel = #"...";

Don't use const with Objective-C objects, they weren't really designed to use it. NSString objects (among many others) are already immutable by default by virtue of their design, so making them const is useless.
As e.James suggested, you can use an NSString * const, which is a constant pointer to an NSString. This is subtly different from a const NSString * (equivalent to NSString const *), which is a pointer to a constant NSString. Using a NSString * const prevents you from reassigning kPoly to point to a new NSString object.

For access from other classes:
.h
extern NSString * const PolygonNumberOfSidesPrefsKey;
.m
NSString * const PolygonNumberOfSidesPrefsKey = #"PolygonNumberOfSides"
For access only inside current class:
.m
static NSString * const kPolygonNumberOfSidesPrefsKey = #"PolygonNumberOfSides"

I would suggest even making the constant more descriptive. A constant for the number of sides of a polygon could come from anywhere. As a suggestion, how about:
kDefaultsPolygonNumberOfSides;
instead.

Related

Unused variable "mystring" warning in iOS

I am taking a Constants.h file for saving all my values. And i declare like this :
static NSString *NAME = #"Name";
static NSString *MAILID = #"Mail-id";
static NSString *ADDRESS = #"Address";
I also use this key in my code. But always its showing a warning Unused variable "NAME"
If i take
const NSString *NAME = #"Name";
Then its showing Error. So please anybody can help me, how can i resolve this warning ?
Thanks :)
In C, and therefore in Objective-C, a static is a variable with file-level scope. Both #import and #include have the same effect as copying and pasting the header file's text into your source file. So with static, each file that includes the header is getting its own personal copy of the symbols NAME, MAILID, etc. If you don't use one the compiler will therefore warn you.
const means constant. If used before the asterisk then it means a constant NSString, which doesn't have a lot of semantic sense because NSString is an opaque type. If you just use const you're also having every file that includes the header redeclare the variables. So you'll get a liker error.
What you probably want in the header is:
extern NSString *const NAME;
That says: (i) estern — not here, but in another unit; and (ii) NSString *const — it is a pointer to NSString and the address within the pointer never changes.
Then in any single implementation file add:
NSString *const NAME = #"NAME";
As otherwise every single compilation unit will sit back and expect one of the others to define it. You'll therefore get a linker error that the symbol is undefined.

NSNumber constants in Obj-C

I want to make some NSNumber constants via the same style used for NSStrings in this topic. That is, I'm creating separate constants.h/.m files and importing them into classes that need to access them.
The trouble with doing this is that there isn't such a thing as a compile-time constant NSNumber. Only NSString gets that distinction. NSNumbers are always created dynamically. You can fake it by using a function that runs at your program's startup to initialize the variables. Your options:
Create a class with a +load method that performs the initialization.
In the file with the constants, include a function with __attribute__((constructor)). So, for example:
// Constants.m
NSNumber *someGlobalNumber;
__attribute__((constructor))
static void InitGlobalNumber() {
someGlobalNumber = [[NSNumber numberWithInteger:1] retain];
}
But of course then you can't reliably use these numbers in any other functions which are run that early in the startup process. This usually isn't a problem, but is worth keeping in mind.
The other option, which I've seen crop up a few times, is to have a class with accessors for the numbers instead of giving raw access to the variables. It's a bit of a heavier design, but it also feels less voodooish, which has its charms.
Unfortunately you cannot currently generate NSNumber constants in the same way you can generate NSString constants. When you try to do you will get a compiler error
NSNumber * const kNumberConstant = #2; // This doesn't work.
However, you can use primitives instead.
NSInteger const kSomeIntValue = 10;
You can basically achieve close to what you want in three parts:
.h file:
extern NSNumber *MyFirstConstant;
.m file
NSNumber *MyFirstConstant;
AppDelegate.m
+(void)initialize
{
MyFirstConstant = #5;
...
}
AppDelegate is guaranteed to run before any of your other code, and the initialize is the first method that would be called on AppDelegate, so you can essentially insure all your constants are setup for you before your app runs.
update:
Years later, I just realized it is possible to create a NSNumber constant for integers... but it's a hack:
#define CONST_INT_NSNUMBER( x ) ((__bridge NSNumber * const)(void * const)(( x << 8 ) | 0x27))
NSNumber * const number = CONST_INT_NSNUMBER(123) ;
This works because certain integer NSNumbers are stored as tagged pointers.
original answer:
You can't do it.
NSNumber * const mynumber = #5.5;
gives:
Initializer element is not a compile-time constant
Implying the compiler has a special feature specifically for creating compile-time constant NSString objects, but not any other type of object.
You could do this, however:
.h:
extern NSNumber * kConstantNumber ;
.m:
NSNumber * kConstantNumber ;
#implementation NSNumber (InitializeConstants)
+(void)load
{
kConstantNumber = #42;
// ... and the rest ...
}
#end

Best practice to store constants for file names and property list names

What would you consider best practice to store constants (like file names for property lists) in objective-c?
I would like to improve the hard coded "Config.plist" in the following code:
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"Config" ofType:#"plist"];
NSDictionary *rows = [[NSDictionary alloc]initWithContentsOfFile:plistPath];
The best solution usually is to move this kind of logic into a model object like MYConfiguration. That way you can query MYConfiguration with explicit methods, and never directly access the NSDictionary.
Regarding the name of that file, there are many approaches to abstracting it, depending on how much reuse you really need (it is possible to go overboard here).
First is the constant. In MYConfiguration.m, you can put a private constant like this:
static NSString * const kConfigurationFileBaseName = #"Config";
You can also have a method that returns the path:
- (NSString *)pathToConfigurationFile {
return [[NSBundle mainBundle] pathForResource:kConfigurationFileBaseName ofType:#"plist"];
}
It's not uncommon to hard-code the name of the file directly in pathToConfigurationFile rather than having a constant. Constants often are only needed in cases where the value is used more than once. If a given constant only appears one time, then moving the value elsewhere can sometimes make the code harder to understand. These are just guidelines, not rules.
If you need the constant itself to be publicly available, then you declare it this way in MYConfiguration.h:
extern NSString * const kMYConfigurationFileBaseName;
and then define it in .m:
NSString * const kMYConfigurationFileBaseName = #"Config";
But the key in all of this is that you define these constants in a header related to the use of the constant. You don't create some central "MYGlobals" dumping ground for everything.
You could use NSUserDefaults
[[NSUserDefaults standardUserDefaults] setObject:(id) forKey:(NSString *)];
[[NSUserDefaults standardUserDefaults] synchronize];
When dealing with objects like NSDictionary, you may have to convert it to an instance of NSData. This can be done with the following methods:
[NSKeyedArchiver archivedDataWithRootObject:(id)];
and
[NSKeyedUnarchiver unarchiveObjectWithData:(NSData *)];
respectively.

newbie objective c pointer question "*" syntax

I'm often getting warnings about passing the wrong pointer types to functions and I just want to once and for all understand this properly.
What is the difference between these two?
CFURLRef *ref=(CFURLRef *)sourceURLArray[0];
CFURLRef ref=(CFURLRef )sourceURLArray[0];
Does the first one actually reference the memory address of the object in the array?
Does the second one make a copy of it?
The main style of code that I'm pursuing at the moment is to have an array of values or objects. I want to be able to use these values, passing them to other functions, without altering the originals.
Leading from that the 2 function declarations I have been trying are the following.
- (void) loadAudioFileIntoMemory:(CFURLRef *)address channel:(int) value row:(int) value2;
- (void) loadAudioFileIntoMemory:(CFURLRef )address channel:(int) value row:(int) value2;
What would the difference be?
CFURLRef is already a pointer -- it's declared as:
typedef const struct __CFURL *CFURLRef;
In general, you will want to use the type CFURLRef without the * when declaring a variable to hold a URL. Most (or all?) of the Core Foundation objects are defined this way, and you can assume a type that ends in Ref is a pointer.
So if you want an array of 10 URLs, you would write something like:
CFURLRef sourceURLArray[10];
//... populate the array.
// Get a particular URL out of the array:
CFURLRef ref = sourceURLArray[0];
What maybe makes this slightly confusing is that the Objective-C counterpart, NSURL, does need a * to make it a pointer as with all Cocoa objects. So you'd write something like:
NSURL *sourceURLArray[10];
...
NSURL *url = sourceURLArray[0];
Finally, you might find it easier to use an NSArray of NSURL objects than using plain C arrays:
NSArray *sourceURLs = [NSArray arrayWithObjects:...];
NSURL *url = [sourceURLs objectAtIndex:0];

How to create constant NSString by concatenating strings in Obj-C?

I'm trying to instanciate a constant NSString by concatanating other NSString instances.
Here is what I'm doing in my implementation file :
static NSString *const MY_CONST = #"TEST";
static NSString *const MY_CONCATENATE_CONST = [NSString stringWithFormat:#"STRING %#", MY_CONST];
It leads to the following compilation error : Initializer element is not constant
I suppose this is because stringWithFormat doesn't return a constant NSString, but since there is no other way to concatenate strings in Obj-C, what am I supposed to do ?
Thanks for your help,
Eric.
I thought there must be a way to do this but the best I could do was using a #define directive. For example,
// Define the base url as an NSString
#define BASE_URL #"http://www.milhouse.co.uk/"
// Now the derived strings glued by magic
NSString *const kBaseURL = BASE_URL;
NSString *const kStatusURL = BASE_URL #"status.html";
NSString *const kBalanceURL = BASE_URL #"balance.html";
static const objects value is determined at compile-time so you indeed cannot add any method calls to their initialization. As an alternative you can do the following:
static NSString *const MY_CONST = #"TEST";
static NSString *MY_CONCATENATE_CONST = nil;
if (nil == MY_CONCATENATE_CONST)
MY_CONCATENATE_CONST = [NSString stringWithFormat:#"STRING %#", MY_CONST];
I think you need to step back and think about if the string needs to be defined as a const.
Clearly the string isn't a constant since you are trying to assign a new value to it - and that is not possible since you specifically instructed the compiler to make sure the value wasn't changed by using the const keyword.
If the string resides as a property in a class you could make it a read-only property - i.e. accessor method but no setter method. You would then be able to construct your string as you wish in the class internally while keeping the callers from changing the value.