iPhone OS: making a switch statement that uses string literals as comparators instead of integers

12,647

Solution 1

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.

Solution 2

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:.

Solution 3

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.

Share:
12,647
nickthedude
Author by

nickthedude

Founder, CEO and Head Hacker at Tiny Mobile Inc. Mostly specialize in iOS but know a little about a lot. I'm a former designer video producer turned coder living in the Bay area.

Updated on June 04, 2022

Comments

  • nickthedude
    nickthedude almost 2 years

    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: 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.