Ways to replace massive if statement with alternative construct in Objective-C - objective-c

I have a fairly lengthy if statement. The if statement examines a string "type" to determine what type of object should be instantiated. Here's a sample...
if ( [type rangeOfString:#"coin-large"].location != NSNotFound )
{
... create large coin ...
mgr = gameLayer.coinLargeMgr;
}
else if ( [type rangeOfString:#"coin-small"].location != NSNotFound )
{
mgr = gameLayer.coinLargeMgr;
}
... more else statements ...
myObject = [mgr getNewObject];
The "else-if" statements continue for other object types which stand at about 20 right now and that number is likely to increase. This works quite well but in terms of maintenance and efficiency I think it could be improved. My leading candidate right now is to create an NSDictionary keyed on the object type string (coin-small, coin-large, etc.) and with the value of the manager object that should be tied to that type. The idea being that this would be a quick look for the type of object I need to create. Not sure this is the best approach, continuing to look at other options but am curious what folks here might have done for a similar problem. Any help/feedback is greatly appreciated.

You can use an NSDictionary filled with ObjC 'Blocks' to do a switch-like statement which executes the desired code. So make a dictionary with your string keys mapped to a block of code to execute when each is found:
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
^{ NSLog(#"found key1"); }, #"key1",
^{ NSLog(#"found key2"); }, #"key2",
nil];
You'll probably prepare this dictionary only once at some early stage like in a constructor or a static initializer so that it is ready when your later code executes.
Then instead of your if/else block, slice out the string key from whatever intput you are receiving (or maybe you won't need to slice it, whatever):
NSString *input = ...
NSRange range = ...
NSString *key = [input substringWithRange:range];
And do the (fast) dictionary lookup for the code to execute. Then execute:
void (^myBlock)(void) = [dict objectForKey:key];
myBlock();

The dictionary approach would be easily doable. Assuming the various managers have been boiled down to specific instances when you create the dictionary, it'd be just the same as almost any object-oriented language:
NSDictionary *stringsToManagers =
[NSDictionary dictionaryWithObjectsAndKeys:
#"coin-large", gameLayer.coinLargeMgr,
#"coin-small", gameLayer.coinSmallMgr,
nil];
// this is assuming that type may contain multiple types; otherwise
// just use [stringsToManagers objectForKey:string]
for(NSString *string in [stringsToManagers allKeys])
{
if([type rangeOfString:string].location != NSNotFound)
{
[[stringsToManagers objectForKey:string] addNewObject];
// or get it and store it wherever it should go
}
}
If all the managers do is vend appropriate objects, the more object-oriented approach might be:
NSDictionary *stringsToClasses =
[NSDictionary dictionaryWithObjectsAndKeys:
#"coin-large", [LargeCoin class],
#"coin-small", [SmallCoin class],
nil];
// this is assuming that type may contain multiple types; otherwise
// just use [stringsToManagers objectForKey:string]
for(NSString *string in [stringsToManagers allKeys])
{
if([type rangeOfString:string].location != NSNotFound)
{
id class = [stringsToManagers objectForKey:string];
id newObject = [[class alloc] init];
// this is exactly the same as if, for example, you'd
// called [[LargeCoin alloc] init] after detecting coin-large
// within the input string; you should obviously do something
// with newObject now
}
}
That could save you having to write any managers if your program structure otherwise fits.

Related

NSMutableDictionary keyEnumerator or NSArray?

I have an NSMutableDictionary with a structure like:
Main Dictionary > Unknown Dictionary > Dictionaries 1,2,4,5,6...
My question is what is the best way to retrieve the Unknown Dictionary key and set it as a variable? This is what I've tried:
NSEnumerator *enumerator = [myMutableDict keyEnumerator];
id aKey = nil;
while ( (aKey = [enumerator nextObject]) != nil) {
id value = [myMutableDict objectForKey:aKey]; // changed to `aKey`
NSLog(#"%#: %#", aKey, value); // tip via rmaddy
}
What goes into objectForKey: if you don't know the name of the object in the key?
The other thought I had was to populate an NSArray, then pulling each of the keys out somehow.
for (NSString *object in myMutableDict)
myArray = [myArray arrayByAddingObject:MainDict];
}
If anyone can suggest a better way to get the object (unknown) from an NSMutableDictionary I'm interested to learn.
You can enumerate dictionaries like this:
NSDictionary * someDictionary = ... however you set your dictionary;
[someDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(#"Key: %#", key);
NSLog(#"Object: %#", obj);
}];
and set:
*stop = YES;
when you find the object you're looking for.
I'm not entirely sure if I understand your question correctly. I assume you have a "main" dictionary with exactly one (unknown) key that maps to another dictionary, which you want to retrieve. This would be a simple and concise way to do this:
NSDictionary *unknownDictionary = mainDictionary[mainDictionary.allKeys.firstObject];
(Yes, some people won't like the dot syntax here, but I find it easier to read in this case. You might also want to add some error checking, for the case that mainDictionary is empty etc.)

Parsing a JSON of unknown Structure Objective-C

I am trying to parse this json and am not sure of parsing this because the keys like "vis-progress-control" might change and I am trying to build a general code which parses this type of a json. I am sure that "type" key will be present in the json structure.
NSDictionary *dict = [sData JSONValue];
NSArray *items = [dict valueForKeyPath:#"assets"];
NSLog(#"%#", items);
for (NSString *key in [[dict objectForKey:#"assets"]allKeys]) {
for (NSString *subKey in [[[dict objectForKey:#"assets"] objectForKey:key]allKeys]) {
NSLog(#"Value at subkey:%# is %#\n",subKey,[[[dict objectForKey:#"assets"]objectForKey:key]objectForKey:subKey]);
}
}
I am using the SBJson Library on Github. My actual issue is How do I access "direction", "degrees" etc when I do not know the "vjs-progress-holder" key?
I have also a widgets array nested within a widgets array. How do I get these values as well?
Read about tree traversal. Sounds like you want to traverse the "tree" of nodes and when you find a particular leaf value you will then traverse back up one level and know the parent is the container you want.
So the idea is that once it's parsed from the JSON, forget it's JSON because now it's just a tree in arrays and dictionaries. Traverse it by getting all the keys in the dictionary via allKeys (returns an array of keys) and then iterate through them getting the associated values (using something like (psuedo code):
for ( NSString * key in allkeysarray) {
NSString * val = [dict objectForKey: key];
if ( [val isEqualToString: #"gradient"] )
{
// now you know that this dictionary is the one you're looking for so you can maybe break out of this loop and just use the keys you know reference these values.
break;
}
}
hopefully that's enough to get you going.
Assuming I'm understanding your objective here, it seems like you want to do something like this?
NSDictionary *outerDict = [sData JSONValue];
NSDictionary *assets = outerDict[#"assets"];
for (NSDictionary *asset in [assets allValues]) {
NSString *type = asset[#"type"];
if ([type isEqualToString:#"gradient"]) {
float degrees = [asset[#"degrees"] floatValue];
// and read whatever other values you need for gradients
}
if ([type isEqualToString:#"image"]) {
// and read the appropriate values for images here...
}
}
So I'll make a different assumption here, which is that you want an array of gradients. So then that looks basically like this:
NSDictionary *outerDict = [sData JSONValue];
NSDictionary *assets = outerDict[#"assets"];
NSMutableArray *gradients = [NSMutableArray array];
for (NSDictionary *asset in [assets allValues]) {
NSString *type = asset[#"type"];
if ([type isEqualToString:#"gradient"]) {
// Add this asset to the list of gradients:
[gradients addObject:asset];
}
if ([type isEqualToString:#"image"]) {
// do something similar for images
}
}
Then, after having done that, if you would like to read the "degrees" field for the 4th gradient, for example, you will find it at gradients[3][#"degrees"]

Create Instance variables at runtime

I want to create instance variables dynamically at runtime, and I want to add these variables to a category. The number of the instance variables may change based on the configuration/properties file which I am using for defining them.
Any ideas??
Use Associative References - this is tricky, but that is the mechanism invented specifically for your use case.
Here is an example from the link above: first, you define a reference and add it to your object using objc_setAssociatedObject; then you can retrieve the value back by calling objc_getAssociatedObject.
static char overviewKey;
NSArray *array = [[NSArray alloc] initWithObjects:# "One", #"Two", #"Three", nil];
NSString *overview = [[NSString alloc] initWithFormat:#"%#", #"First three numbers"];
objc_setAssociatedObject (
array,
&overviewKey,
overview,
OBJC_ASSOCIATION_RETAIN
);
[overview release];
NSString *associatedObject = (NSString *) objc_getAssociatedObject (array, &overviewKey);
NSLog(#"associatedObject: %#", associatedObject);
objc_setAssociatedObject (
array,
&overviewKey,
nil,
OBJC_ASSOCIATION_ASSIGN
);
[array release];
I'd be inclined to just use a NSMutableDictionary (see NSMutableDictionary Class Reference). Thus, you would have an ivar:
NSMutableDictionary *dictionary;
You'd then initialize it:
dictionary = [NSMutableDictionary dictionary];
You can then save values to it dynamically in code, e.g.:
dictionary[#"name"] = #"Rob";
dictionary[#"age"] = #29;
// etc.
Or, if you are reading from a file and don't know what the names of the keys are going to be, you can do this programmatically, e.g.:
NSString *key = ... // your app will read the name of the field from the text file
id value = ... // your app will read the value of the field from the text file
dictionary[key] = value; // this saves that value for that key in the dictionary
And if you're using an older version of Xcode (before 4.5), the syntax is:
[dictionary setObject:value forKey:key];
Depends on exactly what you want to do, the question is vague but if you want to have several objects or several integers or so on, arrays are the way to go. Say you have a plist with a list of 100 numbers. You can do something sort of like this:
NSArray * array = [NSArray arrayWithContentsOfFile:filePath];
// filePath is the path to the plist file with all of the numbers stored in it as an array
That will give you an array of NSNumbers, you can then turn that into an array of just ints if you want like this;
int intArray [[array count]];
for (int i = 0; i < [array count]; i++) {
intArray[i] = [((NSNumber *)[array objectAtIndex:i]) intValue];
}
Whenever you want to get an integer from a certain position, lets say you want to look at the 5th integer, you would do this:
int myNewInt = intArray[4];
// intArray[0] is the first position so [4] would be the fifth
Just look into using a plist for pulling data, it will them be really easy to create arrays of custom objects or variables in your code by parsing the plist.

How to efficiently access large objects in Obj-C using objectForKey and objectAtIndex?

If I have a large NSDirectory typically from a parsed JSON-object I can access this object with code like so:
[[[[[obj objectForKey:#"root"] objectForKey:#"key1"] objectAtIndex:idx] objectForKey:#"key2"] objectAtIndex:idz];
The line might be a lot longer than this.
Can I optimize this code in any way? At least make it easier to read?
This line will also generate a runtime-error if the object does not correspond, what is the most efficient way to avoid that?
If you were using -objectForKey: for everything you could use -valueForKeyPath:, as in
[obj valueForKeyPath:#"key1.key2.key3.key4"]
However, this doesn't work when you need to use -objectAtIndex:. I don't think there's any good solution for you. -valueForKeyPath: also wouldn't solve the problem of the runtime errors.
If you truly want a simple way to do this you could write your own version of -valueForKeyPath: (call it something else) that provides a syntax for specifying an -objectAtIndex: instead of a key, and that does the appropriate dynamic checks to ensure the object actually responds to the method in question.
If you want easier to read code you can split the line into several lines like this
MyClass *rootObject = [obj objectForKey:#"root"];
MyClass *key1Object = [rootObject objectForKey:#"key1"];
MyClass *myObject = [key1Object objectAtIndex:idx];
...
and so forth.
I think, you can create some array, that will contain full "path" to your object. The only thing, you need to store your indexes somehow, maybe in NSNumber, in this case you cannot use NSNumber objects as keys in your dictionaries. Then create a method, that will return needed object for this given "path". smth like
NSMutableArray* basePath = [NSMutableArray arrayWithObjects: #"first", [NSNumber numberWithInt:index], nil];
id object = [self objectForPath:basePath inContainer:container];
- (id) objectForPath:(NSMutableArray*)basePath inContainer:(id)container
{
id result = nil;
id pathComponent = [basePath objectAtIndex: 0];
[basePath removeObjectAtIndex: 0];
// check if it is a number with int index
if( [pathComponent isKindOfClass:[NSNumber class]] )
{
result = [container objectAtIndex: [pathComponent intValue]];
}
else
{
result = [container objectForKey: pathComponent];
}
assert( result != nil );
// check if it is need to continue searching object
if( [basePath count] > 0 )
{
return [self objectForPath:basePath inContainer: result];
}
else
{
return result;
}
}
this is just an idea, but I hope you understand what I mean. And as Kevin mentioned above, if you don't have indexes, you can use key-value coding.
Don't know if it can suit you, but you could also give a try to blocks, I always find them very convenient. At least they made code much more readable.
NSArray *filter = [NSArray arrayWithObjects:#"pathToFind", #"pathToFind2",nil];
NSPredicate *filterBlock = [NSPredicate predicateWithBlock: ^BOOL(id obj, NSDictionary *bind){
NSArray *root = (NSArray*)obj;
// cycle the array and found what you need.
// eventually implementing some sort of exit strategy
}];
[rootObject filteredArrayUsingPredicate:filterBlock];

How does one populate an NSMutable array of NSMutableSets?

I am using this code in a loop to populate an NSMutable Array of NSMutableSets (of NSString objects). The index of the NSSet is based on the length of the word.
// if set of this length not initialized yet, initialize set.
wordIndex = [NSString stringWithFormat:#"%d", currentWordLength];
if ([myWordArray objectForKey:wordIndex] == nil)
[myWordArray setObject:[[NSMutableSet alloc] initWithObjects:currentWord, nil] forKey:wordIndex];
else
[[myWordArray objectForKey:wordIndex] addObject:currentWord];
The final intention is to split up an array of words into an array of sets of words grouped by their lengths.
However, I see that [myWordArray count] is 0 after this. Why?
You are confusing the methods of NSMutableDictionary and NSMutableArray: In Objective-C arrays do not have keys but have indexes. If you change the class for myWordArray to NSMutableDicitionary it should work.
Try this, it looks very much like your logic, but (1) it uses NSNumbers as keys, which makes a little more sense, (2) handles the missing set condition more simply, but just adding the set, and (3) breaks up the source lines somewhat for easier debugging...
NSArray *inputStrings = // however these are initialized goes here
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (NSString *currentString in inputStrings) {
NSInteger currentWordLength = currentString.length;
wordIndex = [NSNumber numberWithInt:currentWordLength];
NSMutableSet *wordSet = [result objectForKey:wordIndex];
if (!wordSet) {
wordSet = [NSMutableSet set];
[result setObject:wordSet forKey:wordIndex];
}
[wordSet addObject:currentWord];
}
If you still have an empty dictionary after running this, it might be simpler to watch what's happening by stepping through it.