172

Is there a more intelligent way to rewrite this?

if ([cardName isEqualToString:@"Six"]) {
    [self setValue:6];
} else if ([cardName isEqualToString:@"Seven"]) {
    [self setValue:7];
} else if ([cardName isEqualToString:@"Eight"]) {
    [self setValue:8];
} else if ([cardName isEqualToString:@"Nine"]) {
    [self setValue:9];
} 
James Raitsev
  • 92,517
  • 154
  • 335
  • 470

14 Answers14

151

Unfortunately they cannot. This is one of the best and most sought after utilizations of switch statements, so hopefully they hop on the (now) Java (and others) bandwagon!

If you are doing card names, perhaps assign each card object an integer value and switch on that. Or perhaps an enum, which is considered as a number and can therefore be switched upon.

e.g.

typedef enum{
  Ace, Two, Three, Four, Five ... Jack, Queen, King

} CardType;

Done this way, Ace would be be equal to case 0, Two as case 1, etc.

Chris
  • 11,819
  • 19
  • 91
  • 145
  • 4
    @abbood For more information about enum, see the posting [NS_ENUM & NS_OPTIONS](http://www.nshipster.com/ns_enum-ns_options/) by Mattt Thompson. – Basil Bourque Oct 21 '13 at 21:32
  • @abbood what is your comment supposed to mean ? Sounds like this is a bad answer but seems Ok to me. Could you explain ? – Alan Andrade May 12 '15 at 22:30
  • As I understand, `CardType` cannot be equal to any enclosed `@""` eg: `[CardType isEqualToString:@"Three"]` – Adro Jul 18 '17 at 07:53
124

You could set up a dictionary of blocks, like this:

NSString *lookup = @"Hearts"; // The value you want to switch on

typedef void (^CaseBlock)();

// Squint and this looks like a proper switch!
NSDictionary *d = @{
    @"Diamonds": 
    ^{ 
        NSLog(@"Riches!"); 
    },
    @"Hearts":
    ^{ 
        self.hearts++;
        NSLog(@"Hearts!"); 
    },
    @"Clubs":
    ^{ 
        NSLog(@"Late night coding > late night dancing"); 
    },
    @"Spades":
    ^{ 
        NSLog(@"I'm digging it"); 
    }
};

((CaseBlock)d[lookup])(); // invoke the correct block of code

To have a 'default' section, replace the last line with:

CaseBlock c = d[lookup];
if (c) c(); else { NSLog(@"Joker"); }

Hopefully Apple will teach 'switch' a few new tricks.

Graham Perks
  • 23,007
  • 8
  • 61
  • 83
85

For me, a nice easy way:

NSString *theString = @"item3";   // The one we want to switch on
NSArray *items = @[@"item1", @"item2", @"item3"];
int item = [items indexOfObject:theString];
switch (item) {
    case 0:
       // Item 1
       break;
    case 1:
       // Item 2
       break;
    case 2:
       // Item 3
       break;
    default:
       break;
}
sbonkosky
  • 2,537
  • 1
  • 22
  • 31
  • 1
    I like this. It answers the needs of most looking for an answer to this problem, it doesn't take much more typing than a similar switch would take in javascript, and it's human readable. – e.w. parris Mar 04 '14 at 00:53
  • 4
    I would not compare this hack with JS switch. What happens if the next programmer add an item between item1 and item2? Too much potential for introducing bugs – Aras Mar 20 '14 at 01:25
  • 1
    its a nice hack though, so I give you thumbs up for the effort :) – Aras Mar 20 '14 at 01:26
  • @Aras If the next programmer needs to add a new entry then they would add it to the end of the array with a new case statement at the end to handle it. So @"item0" can be added after @"item3" in the array, then add a case 3: to handle it. – sbonkosky Mar 21 '14 at 15:56
  • 1
    I totally Like your way. It very neat. I am writing category and need to return UIColor while I have string with me. – Alok C Apr 05 '14 at 12:57
  • I see two issues here. One is performance - while switch is one of the fastest C constructs, this trick is SO slow (search an array for a string every time? yikes). Second is the direct use of array indexes to switch on, while these have no meaning in the switch statement, and are very prone to maintenance errors. I would replace the array with something more "hash"y, that would speed up matching string to number, and would also use an enum when writing the switch. – Motti Shneor May 28 '14 at 22:58
  • My favorite solution. @MottiShneor There is no performance issue here if your array is reasonably small (as 99% will be for a switch statement). The `indexOfObject` operation happens in a fraction of a millisecond. As for your second complaint, I see no issue here, as long as the array is defined only a line or two above the switch statement, this way, it's almost as if you are doing a switch statement with strings. – inorganik Oct 02 '14 at 03:41
  • @MottiShneor I logged before the array declaration and then inside each case of the switch statement. Check the timestamps: `2014-10-01 21:44:39.272 [30979:3589841] declare array` `2014-10-01 21:44:39.272 [30979:3589841] case` – inorganik Oct 02 '14 at 03:48
  • Allow me not to agree. Whereas normal C switch doesn't even make a conditional jump (Assembly) and the compiler calculates the jump from the value in advance (compile-time), you can expect a switch to happen in a matter of few processor cycles. The above solution is about thousands of cycles - maybe tens of thousands. You may not care (and I see you don't) if you measure things in seconds, and for most UI-related uses this is OK, I would never have such code in a "backend" or "engine" part of my application. – Motti Shneor Oct 02 '14 at 12:47
  • This will not work as the NSString objects are different despite having the same string content. – aronspring Sep 17 '15 at 16:23
  • @aronspringfield It works. `indexOfObject:` uses `isEqual:` on the objects in the array, and NSString's implementation of `isEqual:` tests the value of the string, not the pointer. – sbonkosky Sep 17 '15 at 20:43
11

Unfortunately, switch statements can only be used on primitive types. You do have a few options using collections, though.

Probably the best option would be to store each value as an entry in an NSDictionary.

NSDictionary *stringToNumber = [NSDictionary dictionaryWithObjectsAndKeys:
                                              [NSNumber numberWithInt:6],@"Six",
                                              [NSNumber numberWithInt:7],@"Seven",
                                              [NSNumber numberWithInt:8],@"Eight",
                                              [NSNumber numberWithInt:9],@"Nine",
                                              nil];
NSNumber *number = [stringToNumber objectForKey:cardName];
if(number) [self setValue:[number intValue]];
ughoavgfhw
  • 39,734
  • 6
  • 101
  • 123
9

A bit late but for anyone in the future I was able to get this to work for me

#define CASE(str) if ([__s__ isEqualToString:(str)])
#define SWITCH(s) for (NSString *__s__ = (s); ; )
#define DEFAULT
Newyork167
  • 494
  • 5
  • 9
6

Here is the more intelligent way to write that. It's to use an NSNumberFormatter in the "spell-out style":

NSString *cardName = ...;

NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
[nf setNumberStyle:NSNumberFormatterSpellOutStyle];
NSNumber *n = [nf numberFromString:[cardName lowercaseString]];
[self setValue:[n intValue]];
[nf release];

Note that the number formatter wants the string to be lowercased, so we have to do that ourselves before passing it in to the formatter.

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
5

There are other ways to do that, but switch isn't one of them.

If you only have a few strings, as in your example, the code you have is fine. If you have many cases, you could store the strings as keys in a dictionary and look up the corresponding value:

NSDictionary *cases = @{@"Six" : @6,
                        @"Seven" : @7,
                        //...
                       };

NSNumber *value = [cases objectForKey:cardName];
if (value != nil) {
    [self setValue:[value intValue]];
}
Caleb
  • 124,013
  • 19
  • 183
  • 272
4

BY FAR.. my FAVORITE "ObjC Add-On" is ObjectMatcher

objswitch(someObject)
    objcase(@"one") { // Nesting works.
        objswitch(@"b")
            objcase(@"a") printf("one/a");
            objcase(@"b") printf("one/b");
            endswitch // Any code can go here, including break/continue/return.
    }
    objcase(@"two") printf("It's TWO.");  // Can omit braces.
    objcase(@"three",     // Can have multiple values in one case.
        nil,              // nil can be a "case" value.
        [self self],      // "Case" values don't have to be constants.
        @"tres", @"trois") { printf("It's a THREE."); }
    defaultcase printf("None of the above."); // Optional default must be at end.
endswitch

AND it works with non-strings, TOO... in loops, even!

for (id ifNumericWhatIsIt in @[@99, @0, @"shnitzel"])
    objswitch(ifNumericWhatIsIt)
        objkind(NSNumber)  printf("It's a NUMBER.... "); 
        objswitch([ifNumericWhatIsIt stringValue])
            objcase(@"3")   printf("It's THREE.\n"); 
            objcase(@"99")  printf("It's NINETY-NINE.\n"); 
            defaultcase     printf("some other Number.\n");
       endswitch
    defaultcase printf("It's something else entirely.\n");
endswitch

It's a NUMBER.... It's NINETY-NINE.
It's a NUMBER.... some other Number.
It's something else entirely.

Best of all, there are SO few {...}'s, :'s, and ()'s

Alex Gray
  • 16,007
  • 9
  • 96
  • 118
3

Objective-c is no different from c in this aspect, it can only switch on what c can (and the preproc def's like NSInteger, NSUInteger, since they ultimately are just typedef'd to an integral type).

Wikipedia:

c syntax:

The switch statement causes control to be transferred to one of several statements depending on the value of an expression, which must have integral type.

Integral Types:

In computer science, an integer is a datum of integral data type, a data type which represents some finite subset of the mathematical integers. Integral data types may be of different sizes and may or may not be allowed to contain negative values.

chown
  • 51,908
  • 16
  • 134
  • 170
2

I'm kind of late to the party, but to answer the question as stated, there's a more intelligent way:

NSInteger index = [@[@"Six", @"Seven", @"Eight", @"Nine"] indexOfObject:cardName];
if (index != NSNotFound) [self setValue: index + 6];

Note that indexOfObject will look for the match using isEqual:, exactly as in the question.

ilya n.
  • 18,398
  • 15
  • 71
  • 89
2

Building on @Graham Perks idea posted earlier, designed a simple class to make switching on strings fairly simple and clean.

@interface Switcher : NSObject

+ (void)switchOnString:(NSString *)tString
                 using:(NSDictionary<NSString *, CaseBlock> *)tCases
           withDefault:(CaseBlock)tDefaultBlock;

@end

@implementation Switcher

+ (void)switchOnString:(NSString *)tString
                 using:(NSDictionary<NSString *, CaseBlock> *)tCases
           withDefault:(CaseBlock)tDefaultBlock
{
    CaseBlock blockToExecute = tCases[tString];
    if (blockToExecute) {
        blockToExecute();
    } else {
        tDefaultBlock();
    }
}

@end

You would use it like this:

[Switcher switchOnString:someString
                   using:@{
                               @"Spades":
                               ^{
                                   NSLog(@"Spades block");
                               },
                               @"Hearts":
                               ^{
                                   NSLog(@"Hearts block");
                               },
                               @"Clubs":
                               ^{
                                   NSLog(@"Clubs block");
                               },
                               @"Diamonds":
                               ^{
                                   NSLog(@"Diamonds block");
                               }
                           } withDefault:
                               ^{
                                   NSLog(@"Default block");
                               }
 ];

The correct block will execute according to the string.

Gist for this solution

Chuck Krutsinger
  • 2,830
  • 4
  • 28
  • 50
2

You can use macros approach to achieve it:

#define CASE(str)  if ([__s__ isEqualToString:(str)]) 
#define SWITCH(s)  for (NSString *__s__ = (s); ; )
#define DEFAULT   

SWITCH (string) {
    CASE (@"TestString") {
        break;
    }
    CASE (@"YetAnotherString") {
        break;
    }
    CASE (@"Test") {
        break;
    }
    DEFAULT {
        break;
    }
 }
Argus
  • 2,241
  • 1
  • 22
  • 27
0

I can't Comment on cris's answer on @Cris answer but i would like to say that:

There is an LIMITATION for @cris's method:

typedef enum will not take alphanumeric values

typedef enum
{
  12Ace, 23Two, 23Three, 23Four, F22ive ... Jack, Queen, King

} CardType;

So here is another One:

Link Stack over flow Go to this user answer "user1717750"

Community
  • 1
  • 1
Puru
  • 32
  • 7
-1
typedef enum
{
    Six,
    Seven,
    Eight
} cardName;

- (void) switchcardName:(NSString *) param {
    switch([[cases objectForKey:param] intValue]) {
        case Six:
            NSLog(@"Six");
            break;
        case Seven:
            NSLog(@"Seven");
            break;
        case Eight:
            NSLog(@"Eight");
            break;
        default: 
            NSLog(@"Default");
            break;
    }
}

Enjoy Coding.....

Donald Duck
  • 8,409
  • 22
  • 75
  • 99
Ek SAD.
  • 17
  • 4