views:

325

answers:

3

So i'd like to do this:

switch (keyPath) {
    case @"refreshCount":
        //do stuff
    case @"timesLaunched":
       //do other stuff
}

but apparently you can only use integers as the switch quantity. Is the only way to do this parse the string into an integer identifier and then run the switch statement?

like this:

nsinteger num = nil;

if (keyPath isEqual:@"refreshCount") {

num = 0

}

if (keyPath isEqual:@"timesLaunched") {

num = 1

}

I'm trying to optimize this code to be as quick as possible because its going to get called quite often.

thanks,

Nick

NOTE: Yes I'm using KVO so I am recieving a string in the "callback."

NOTE #2 Electric boogaloo: So what made me first consider the switch statement is my original code implementation was like this:

if ([keyPath isEqual:@"refreshCount"] && ([[change valueForKey:@"new"] intValue] == 10)) { //  
NSLog(@"achievemnt hit inside");
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"title" message:@"Achievement Unlocked!" delegate:self cancelButtonTitle:@"cancel" otherButtonTitles:nil];
    [alert show];

I want to do a bunch of these with different XX values all in the same method:

if ([keyPath isEqual:@"refreshCount"] && ([[change valueForKey:@"new"] intValue] == 10)) {

//unlock small achievement

}
 if ([keyPath isEqual:@"refreshCount"] && ([[change valueForKey:@"new"] intValue] == 50))   {

//unlock bigger achievement

}   

//etc

This just seemed very inefficient to me but maybe I'm wrong.

+2  A: 

Why not just use an enum?

typedef enum _KeyPath
{
     KeyPathNone,
     KeyPathRefreshCount,
     KeyPathTimesLaunched,
     KeyPathCount
} KeyPath;

If you must use strings that you should be comparing with isEqualToString:

From the NSString docs:

Special Considerations
When you know both objects are strings, this method is a faster way to check equality than isEqual:.
jessecurry
Very likely because he is using KVO or KVC and, thus, needs the paths as NSStrings.
bbum
yes I'm using KVO so I'm getting the string sent back, see the added note in the question.
nickthedude
In that case use the `isEqualToString:` method, and avoid the group conditionals as it will evaluate the first part of the conditional over and over. Instead use a nested conditional. If a big achievement may only be reached after a small one, just make your achievement granting logic reflect that fact.
jessecurry
+2  A: 

The short answer is to not use strings in the first place. Barring that, you can have the strings be keys in a dictionary with integer (NSNumber) values. Or you can use the hashes of the strings.

switch ( [keyPath myQuickHash] ) {
case kHashRefreshCount:
case kHashTimesLaunched:
}

If there are just a few distinct strings, you can use the first (or last) 4 characters as a string literal and consider that the hash.

switch ( [keyPath stringLiteral] ) {
case 'refr':
case 'time':
}

Edit:

A switch statement is essentially a sparse array of code snippets. A hash table is a means of looking up an index in a sparse array given an arbitrary value. Given a known set of string inputs, a switch statement can operate like a hash table, which is to say, have constant lookup time. All you have to do is choose a hash algorithm with no collisions for the known inputs. If the set of inputs is not known, this is not an option, but in this question it is possible that all inputs are known.

This has absolutely nothing to do with how Apple implements their hash algorithm because you have to implement your own hash algorithm. The algorithm chosen could probably be as simple as adding up the length and letters in the string.

drawnonward
You might not want to use hashes, as a very small but non-zero chance of a collision could give rise to odd bugs. An `enum` is probably cleaner and safer.
Alex Reynolds
Hashes are slow. Very very slow. If we are talking, like, 5 different keys and they are all constant NSStrings, straight up cascading `if(... isEqualToString: ...)` tests will very likely be faster.
bbum
Comparing `-hash` to a constant won't work. The `hash` of an object is only constant at runtime. Using a substring operation may be faster, maybe not -- depends on the set of strings.
bbum
The hashes would have been calculated at compile time. If there were collisions, a different algorithm would be chosen. The hash would be the same speed as a single call to isEqualToString, unless you are thinking this needs a cryptographic hash.
drawnonward
@bbum the point of hashes is that equal input gives equal output. What part of that changes between compile time and run time?
drawnonward
In your code above, the hash is calculated at runtime unless `keyPath` is no longer a standard NSString.
bbum
*equal input gives equal output* **only holds for a single runtime session**. It is not true across multiple runs of an application. Many classes just `return self;` as the implementation of `-hash` (`NSObject`, for example, IIRC).
bbum
@bbum you should read up on hashes. You are arguing that the sum of 1+2+3 may change between runtime sessions. I was not suggesting to use the system hash, even though my original code snippet implied it. Even with the system hash, strings return a hash based on content.
drawnonward
In Cocoa, as in many systems, a hash is only guaranteed to be constant for any given object for that runtime session. Again; NSObject returns `self` as the hash value. The default behavior of Cocoa objects that do not customize the `hash` method ensures non-constant values across runtime sessions in all but the rarest, lottery draw winning chances, cases.
bbum
Furthermore, the algorithm behind the `-hash` method of NSString has changed across different releases of Mac OS X (the original algorithm guaranteed collisions in a rather common case and was optimized). So, no, hashes *in Cocoa* are not constant across runtime sessions. That you claim they are is a rule that is not necessarily inherent to hashes.
bbum
+2  A: 

That is, for a cast statement to work, it would just have to call isEqualToString: anyway and would be just as slow, but probably not anywhere near as slow as you think.

The first question, of course, is have you measured performance and do you have evidence that the code is causing a performance issue?

If not, go and finish your app. A shipping app always outperforms an app still in development!

I would bet you don't have a performance issue; if all of your strings really are inline string constants -- @"refreshCount" and the like -- related to key-value observing, then it is quite likely that all of them are constant strings compiled into the app and, thus, comparison will be really fast because every time you mention "@refreshCount" it really is the same string at the same address (which compares very fast).

If you do have a quantifiable performance issue, post the details in a different question and someone can answer specifically. As it is, there isn't enough architectural or quantitative information to do anything more than speculate.

bbum
you're right I havent coded it in to see if there is an issue, I just sat down and started writing the switch statement when I wanted to fully impliment the method because I thought it would be much more efficient. BTW this: "A shipping app always outperforms an app still in development!" is awesome! thanks for that. I'm adding another note about my initial implementation that made me question the efficiency in the first place.
nickthedude
bbum