2013-07-15 04:33:17 6, er, 8 ways to do the same thing
Playing with some different ways to do the same thing in Objective-C. They all do the exact same job. I think #2 ends up being my preferred form.

The general idea is that you have a string you need to compare against a set of possible values and then assign a variable a certain enum value based on that.

Update: I should have mentioned that I was mentally assuming that the -isEqualToString: comparison could become more complex. That said, as given there are two other fine ways to skin this cat, as pointed out by Dmitri and Rainer, just moments after tweeting the link. I've added them as options 7 and 8 below. If the specific case of -isEqualToString: is at issue, then I think option #8 is the most elegant.

( objc )
  1  #import <Foundation/Foundation.h>
2
3 // compile with: clang -fobjc-arc -o test test.m -framework Foundation
4
5 #define A @"this"
6 #define B @"that"
7 #define C @"other"
8 #define D @"default"
9
10 typedef enum { a=0, b, c, d } letterType;
11
12 int main() {
13 @autoreleasepool {
14
15 for (NSString *test in @[ @"other", @"nonsense" ]) { // expected results: "other" and "default"
16
17 letterType type;
18 NSArray *array = @[ A, B, C, D ];
19
20 void (^ log)(int) = ^(int i) { NSLog( @"%@ => %@", test, array[ i ] ); };
21
22 // 6 ways to do the same thing (that thing being #0):
23
24 // 0. what i was originally trying to make better:
25
26 if ([test isEqualToString:A])
27 type = a;
28 else if ([test isEqualToString:B])
29 type = b;
30 else if ([test isEqualToString:C])
31 type = c;
32 else
33 type = d;
34
35 log(type);
36
37 // 1. for short options, this can be an elegant and easy to follow form.
38
39 type = [test isEqualToString:A] ? a :
40 [test isEqualToString:B] ? b :
41 [test isEqualToString:C] ? c : d;
42
43 log(type);
44
45 // 2. longer possible lists can benefit from factoring out the comparison.
46 // note: the default is placed at the front of the array
47 // note: a failed comparison returns NSIntegerMax, when cast to an int it is -1
48
49 int types[] = { d,a,b,c };
50 NSUInteger index = [array indexOfObjectPassingTest:^BOOL(id s, NSUInteger i, BOOL *stop) {
51 return [test isEqualToString:(NSString *)s];
52 }];
53 type = types[1+(int)index];
54
55 log(type);
56
57 // 3. The last two lines of #2 can be folded together. A touch more obscure.
58
59 //int types[] = { d,a,b,c };
60 type = types[1+[array indexOfObjectPassingTest:^BOOL(id s, NSUInteger i, BOOL *stop) {
61 return [test isEqualToString:(NSString *)s];
62 }]];
63
64 log(type);
65
66 // 4. using c99 inline arrays. harder to see the logic.
67
68 index = [array indexOfObjectPassingTest:^BOOL(id s, NSUInteger i, BOOL *stop) {
69 return [test isEqualToString:(NSString *)s];
70 }];
71 type = (int[]){ d,a,b,c }[1+(int)index];
72
73 log(type);
74
75 // 5. finally, as a "one-liner". less readable, harder to debug, especially ugly with long arrays.
76
77 type = (int[]){ d,a,b,c }[1+[array indexOfObjectPassingTest:^BOOL(id s, NSUInteger i, BOOL *stop) {
78 return [test isEqualToString:(NSString *)s];
79 }]];
80
81 log(type);
82
83 // 6. using blocks to clean up the core logic.
84 // so, as with most blocks, this depends on how often you plan to use it.
85 // lots of ugly up front, but then implementation becomes the most elegant.
86
87 //letterType types[] = { d,a,b,c };
88 letterType (^typeFromOptionsAndArrayMatchingString)(letterType *, NSArray *, NSString *) =
89 ^letterType(letterType *types, NSArray *a, NSString *s) {
90
91 return types[1+[a indexOfObjectPassingTest:^BOOL(id s, NSUInteger i, BOOL *stop) {
92 return [test isEqualToString:(NSString *)s];
93 }]];
94 };
95
96 type = typeFromOptionsAndArrayMatchingString(types, array, test);
97
98 log(type);
99
100 // 7. dictionary with castings. Credit: Dmitri.
101
102 NSDictionary *associations = @{ A : @(a), B : @(b), C : @(c), test : @(d) };
103 type = (letterType)[associations[test] intValue];
104
105 log(type);
106
107 // 8. Arrays using test in the lookup array. Credit: Rainer and Dmitri.
108
109 type = (letterType)[@[A, B, C, test] indexOfObject:test];
110
111 log(type);
112 }
113 }
114 }


the output will basically be:
  1  2013-07-15 17:02:33.500 test[83393:707] other => other
2 2013-07-15 17:02:33.502 test[83393:707] other => other
3 2013-07-15 17:02:33.502 test[83393:707] other => other
4 2013-07-15 17:02:33.502 test[83393:707] other => other
5 2013-07-15 17:02:33.503 test[83393:707] other => other
6 2013-07-15 17:02:33.503 test[83393:707] other => other
7 2013-07-15 17:02:33.503 test[83393:707] other => other
8 2013-07-15 17:02:33.504 test[83393:707] other => other
9 2013-07-15 17:02:33.504 test[83393:707] other => other
10 2013-07-15 17:02:33.504 test[83393:707] nonsense => default
11 2013-07-15 17:02:33.504 test[83393:707] nonsense => default
12 2013-07-15 17:02:33.505 test[83393:707] nonsense => default
13 2013-07-15 17:02:33.505 test[83393:707] nonsense => default
14 2013-07-15 17:02:33.505 test[83393:707] nonsense => default
15 2013-07-15 17:02:33.506 test[83393:707] nonsense => default
16 2013-07-15 17:02:33.506 test[83393:707] nonsense => default
17 2013-07-15 17:02:33.506 test[83393:707] nonsense => default
18 2013-07-15 17:02:33.506 test[83393:707] nonsense => default
  • David H (Mon, July 15th, 2013, 9:39pm UTC)
    So what is the difference in compiled (with -Os) size?

  • Jeff (Mon, July 15th, 2013, 10:17pm UTC)
    #0: 9.7k
    #1: 9.7k
    #2: 14k
    #3: 14k
    #4: 14k
    #5: 14k
    #6: 14k
    #7: 9.8k
    #8: 9.6k

    and if you compile the version with all of them in there: 15k. :o)

  • Andrew G (Tue, July 16th, 2013, 2:22pm UTC)
    I like #7 because I like seeing the mapping of strings to enum values in one place. It would also allow, if the number of strings gets large, the mapping to be externalized in a JSON file (or plist).

  • Jeff (Tue, July 16th, 2013, 5:53pm UTC)
    Good points, Andrew. Solution #8 suffers from the array having to match the enum fields in number and order. #7 makes clear the intent and the associations, and can be easily edited without having to change the enum order, which might need to be fixed. It's harder to break than #8.

Leave a comment