Accessing constants using Key-Value Coding in Objective-C - objective-c

I'm making a program that has a lot of constants. I decided to put them all into a separate class and I'm importing it by the classes that need it. The files look similar to this
// Constants.h
extern const int baseCostForBuilding;
extern const int maxCostForBuilding;
// etc
// Constants.m
const int baseCostForBuilding = 400;
const int maxCostForBuilding = 1000;
// etc
What I'm trying to do is access them using key-value coding. What I've tried so far hasn't worked.
id object = [self valueForKey:#"baseCostForBuilding"];
But I can do the following and it works fine.
id object = baseCostForBuilding;
This may seem pointless but I have a lot of variables that have to end in "CostForBuilding" and the function I need this in only gets the first part of the string. Example, "base", "max", "intermediate", etc. It will then combine it with "CostForBuilding" or something else to get the variable name.
If this is possible, it would be way nicer to only have one or two lines of code instead of multiple if-statements to access the correct variable. Does anyone know a way to do this? Thanks in advance.

You can fill a dictionary with the appropriate values:
- (id)init
{
...
buildingCosts = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt:100], #"base",
[NSNumber numberWithInt:200], #"max",
...,
nil];
...
}
- (int)buildingCostForKey:(NSString *)key
{
return [(NSNumber *)[buildingCosts objectForKey:key] intValue];
}
- (void)dealloc
{
[buildingCosts release];
}
Which you could then use as follows:
int baseCost = [myClass buildingCostForKey:#"base"];

Related

Identifying properties in the header file vs. implementation file

I have been interested in using something along the the lines of the following code to
automate the building of my objects (since there are many of them with quite a few properties):
MyObject *myObject = [[myObject alloc] init];
unsigned int numberOfProperties = 0;
objc_property_t *propertyArray = class_copyPropertyList([MyObject class], &numberOfProperties);
for (NSUInteger i = 0; i < numberOfProperties; i++)
{
objc_property_t property = propertyArray[i];
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
if (propertyName)
{
id valueForProperty = [myObject valueForKey:propertyName];
[myObject setValue:valueForProperty forKey:propertyName];
}
}
free(propertyArray);
However, what I've noticed is that this code will try to run not just on the properties in my header file, but also all of my implementation properties as well, which I do not want.
Since Objective-C doesn't actually distinguish public vs private properties, I am not sure how to do this. Any thoughts on how to indicate that I'm only interested in the properties in the header file to simulate the same thing effectively?
In short, you don't. This information is not available in the compiled program. You'd need to write a custom preprocessor to do this if you really wanted to.

set ivars from NSDictionnary

I'm currently working on a project where the user defines some parameters in a NSDictionnary, that I'm using to setup some objects.
For example, you can ask to create a Sound object with parameters param1=xxx, param2=yyy, gain=3.5 ... Then an Enemi object with parameters speed=10, active=YES, name=zzz ...
{
active = NO;
looping = YES;
soundList = "FINAL_PSS_imoverhere_all";
speed = 100.0;
}
I then instantiate my classes, and would like to set the ivars automatically from this dictionnary.
I've actually wrote some code to check that this parameter exists, but I'm having trouble in actually setting the parameter value, especially when the parameter is non object (float or bool).
Here's what I'm doing so far :
//aKey is the name of the ivar
for (NSString *aKey in [properties allKeys]){
//create the name of the setter function from the key (parameter -> setParameter)
NSString *setterName = [aKey stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[aKey substringToIndex:1] uppercaseString]];
setterName = [NSString stringWithFormat:#"set%#:",setterName];
SEL setterSelector = NSSelectorFromString(setterName);
//Check if the parameter exists
if ([pge_object respondsToSelector:setterSelector]){
//TODO : automatically set the parameter
}
else{
[[PSMessagesChecker sharedInstance]logMessage:[NSString stringWithFormat:#"Cannot find %# on %#", aKey, [dict objectForKey:#"type"]] inColor:#"red"];
NSLog(#"Cannot find %# on %#", aKey, [dict objectForKey:#"type"]);
}
}
}
As you can see, I don't know what to do once I've found that the parameter exists on the object. I tried to use "performSelector... withObject..., but my problem is that some of the parameters are non-objects (float or bool).
I also tried to get the class of the parameter, by using the setter, but it didn't help.
Did anyone manage to do something like that?
Jack Lawrence's comment is spot on.
What you are looking for is called Key Value Coding, or just KVC.
This fundamental part of Cocoa lets you get and set any instance variable using its name as a String and a new value.
It will automatically handle coercing Objects to primitive values, so you can use it for int and float properties too.
There is also support for validating values and handling unknown properties.
see the docs
your code, without validation, could be written
for( id eachKey in props ) {
[anOb setValue:props[eachKey] forKey:eachKey];
}
or just
[anOb setValuesForKeysWithDictionary:props];
as Jack said.
For the non-object parameters you have to put them into an object, for example NSNumber or NSValue. You can then add these objects into your dictionary.
For Example:
float f = 0.5;
NSNumber f_obj = [NSNumber numberWithFloat:f];

How to add enum as datasource in uipickerview?

I have created one enum in my ApplicationConstants.h file and enum is as below.
typedef enum {
CurrentLocation = 0,
CurrentCity,
CurrentRegion } enumLocation;
Now the question is I dont know how to set this enum as data source in UIPickerView ! Can anyone give me idea about this ? I have used NSArray as datasource before. But I want enum now. Because of this problem I stuck. Can you help me ? So i can go ahead in my application.
You can't display the identifier of an enum directly. You have to prepare a big if/else/switch block to prepare a string for each element, or add strings for every element in an array and select by index from there.
Enum types are usually used instead of magic numbers in programs and thus are designed to make the code easier to read by a human, but the underlying representation is in one of the base data types (int, char, etc).
For UIPickerViewDataSource
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
return LOCATION_COUNT;
}
Use the trick for enum count
typedef enum { CurrentLocation = 0, CurrentCity, CurrentRegion, LOCATION_COUNT} enumLocation;
for UIPickerViewDelegate
- (NSString *)pickerView:(UIPickerView *)thePickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
[self.enumLocationStringDict objectForKey:[NSNumber numberWithInt:row]]
}
Dictionary can be as follow
self.enumLocationStringDict = [NSDictionary dictionaryWithObjectsAndKeys:
#"CurrentLocation", [NSNumber numberWithInt:CurrentLocation],
#"CurrentCity", [NSNumber numberWithInt:CurrentCity],
#"CurrentRegion", [NSNumber numberWithInt:CurrentRegion],
,nil];
you can choose the way to define a simple macro for it for instace, like this:
in the header file (.h):
#define NAMEOF(var) ##var
typedef enum : NSInteger {
CustomTypeUknown = 0,
CustomTypeSomething,
CustomTypeParticularValue,
CustomTypeBoringValue,
} CustomType;
in the implementaion file (.m)
NSArray *_array = [NSArray arrayWithObjects:NAMEOF(CustomTypeUknown), NAMEOF(CustomTypeSomething), NAMEOF(CustomTypeParticularValue), NAMEOF(CustomTypeBoringValue), nil];
the objects will be strings with the names, but for safety sake you could check the values via logging _array:
NSLog(#"%#", _array);
it should look like this:
(
CustomTypeUknown,
CustomTypeSomething,
CustomTypeParticularValue,
CustomTypeBoringValue
)
if I haven't misunderstood your problem, that is you looked for...

Objective-c: Dynamic Class Names

I'm not sure if I worded the subject correctly. I am looping through an array, within each loop I am trying to instantiate a class, but I want to dynamically create the name. Like so:
int i = 0;
for(NSString* thisdatarow in filedata) {
i++;
NSString* thisad = [NSString stringWithFormat:#"ad%d", i];
NSLog(#"%#", thisad);
AdData* thisad = [AdData new];
}
In the example above I want AdData* thisad... to be named dynamically - "ad1", "ad2", "ad3"...and so on. I get a conflicting type error.
This code also generated an error:
int i = 0;
for(NSString* thisdatarow in filedata) {
i++;
AdData* [NSString stringWithFormat:#"ad%d", i] = [AdData new];
}
Is there a way to do this?
You can't do that in Objective-C.
Use a NSString to AdData map--it'll do basically the same thing!
**edit: To clarify, use an:
NSMutableDictionary *dict;
with keys that are NSString* objects containing the ad names, and values that are the AdData* objects.
i.e.
[dict setValue:ad1 forKey:#"ad1"];
to set the values, and
[dict valueForKey:#"ad1"];
to get the values. (ignore the obvious memory leaks there with the strings...)
This isn't possible. While Objective-C is very dynamic, it's not that dynamic.
The suggested way to do this would be to create your instances and put them into an array, not assigning them to explicitly named variables.
You can then refer to them individually using their index in the array.
Something like this:
NSMutableArray *ads = [NSMutableArray array];
for(NSString* thisdatarow in filedata) {
AdData* thisad = [[[AdData alloc] init] autorelease];
[ads addObject:thisad];
}
// get third ad:
AdData *ad = [ads objectAtIndex:2];
Alternatively you could create an NSDictionary, if you really want to refer to them by a name, like this:
NSMutableDictionary *ads = [NSMutableDictionary dictionary];
int i = 0;
for(NSString* thisdatarow in filedata) {
i++;
AdData* thisad = [[[AdData alloc] init] autorelease];
NSString *keyName = [NSString stringWithFormat:#"ad%d", i];
[ads setObject:thisad forKey:keyName];
}
// get third ad
AdData *ad = [ads objectForKey:#"ad2"];
Cant be done Without using a C array, which would look like this:
AdData **ad = malloc(sizeof(AdData) * numberOfAds);
ad[1] = [AdData new];
// etc.
if (ad)
free(ad);
But I don't know how that would work because of how Objective-C classes are stored....
Local variable names are a purely compile-time concept. So you cannot do anything "dynamic" (i.e. at runtime) with it. The compiler is free to rename the variables and add or remove variables as it sees fit.
If you think about it, what is the point of dynamically manipulating local variable names? In order to use the dynamically-named variable again, you must either 1) explicitly refer to the variable name, in which case you have hard-coded the name (not so dynamic), or 2) dynamically construct the name again. If it's (1), then there is only a fixed set of variable names, so dynamic-ness is unnecessary. If it's (2), you're missing the point of local variable names (the whole point of which is so they can be referred to explicitly).

Is there a concise way to map a string to an enum in Objective-C?

I have a string I want to parse and return an equivalent enum. I need to use the enum type elsewhere, and I think I like how I'm defining it. The problem is that I don't know a good way to check the string against the enum values without being redundant about the order of the enums.
Is there no option other than a big if/else?
typedef enum {
ZZColorRed,
ZZColorGreen,
ZZColorBlue,
} ZZColorType;
- (ZZColorType)parseColor:(NSString *)inputString {
// inputString will be #"red", #"green", or #"blue" (trust me)
// how can I turn that into ZZColorRed, etc. without
// redefining their order like this?
NSArray *colors = [NSArray arrayWithObjects:#"red", #"green", #"blue", nil];
return [colors indexOfObject:inputString];
}
In Python, I'd probably do something like the following, although to be honest I'm not in love with that either.
## maps url text -> constant string
RED_CONSTANT = 1
BLUE_CONSTANT = 2
GREEN_CONSTANT = 3
TYPES = {
'red': RED_CONSTANT,
'green': GREEN_CONSTANT,
'blue': BLUE_CONSTANT,
}
def parseColor(inputString):
return TYPES.get(inputString)
ps. I know there are color constants in Cocoa, this is just an example.
try this: Map enum to char array
Pseudo code.. untested.
int lookup(const char* str) {
for(name = one; name < NUMBER_OF_INPUTS; name++) {
if(strcmp(str, stats[name]) == 0) return name;
}
return -1;
}
A more objective-c'ish version of the code could be:
// build dictionary
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
for(i=0; i<number_of_strings; i++) {
[dict setObject:[NSNumber numberWithInteger:i] forKey:[NSString stringWithUTF8String:names[i]]];
}
// elsewhere... lookup in dictionary
id obj = [dict objectForKey:name];
if(obj) return [obj intValue];
return -1;
This has already been answered: Converting between C enum and XML
Basically, you wind up defining corresponding strings when you define your enum, and then you use a category on NSArray so that you can do this:
static NSArray* colorNamesArray = [[NSArray alloc] initWithObjects:colorNames];
//colorNames is a nil-terminated list of string literals #defined near your enum
NSString* colorName = [colorNamesArray stringWithEnum:color];
//stringWithEnum: is defined with a category
Sure, the #define is a little ugly, but the code above, which is what you'll work with most of the time, is actually pretty clean.
I was never satisfied with any of the suggestions. (But I appreciate the effort that went into them.) I tried a few of them but they didn't feel good or were error-prone in practice.
I ended up created a custom dictionary to map integers to strings which feels a lot better because it's Cocoa through and through. (I didn't subclass NSDictionary in order to make it harder to misuse.)
#interface ZZEnumDictionary : NSObject {
NSMutableDictionary *dictionary;
}
+ (id)dictionary;
+ (id)dictionaryWithStrings:(id)firstString, ...;
- (NSString *)stringForInt:(NSInteger)intEnum;
- (NSInteger)intForString:(NSString *)stringEnum;
- (BOOL)isValidInt:(NSInteger)intEnum;
- (BOOL)isValidString:(NSString *)stringEnum;
- (BOOL)stringEquals:(NSString *)stringEnum intEnum:(NSInteger)intEnum;
- (BOOL)setContainsString:(NSSet *)set forInt:(NSInteger)intEnum;
- (NSArray *)allStrings;
#end
#interface ZZEnumDictionary ()
- (void)setInt:(NSInteger)integer forString:(NSString *)string;
#end