【问题标题】:the best way to implement readable switch case with strings in objective-c?在objective-c中用字符串实现可读的switch case的最佳方法?
【发布时间】:2011-08-30 14:24:49
【问题描述】:

在 ruby​​、javascript 等其他动态语言中,您可以这样做:

switch(someString) {
    case "foo":
       //do something;
       break;
    case "bar":
       // do something else;
       break;
    default:
       // do something by default;
}

在objective-c 中,因为它是从c 语言派生而来的,所以你不能这样做。我的最佳做法是:

#import "CaseDemo.h"

#define foo 1
#define bar 2

static NSMutableDictionary * cases;

@implementation CaseDemo

- (id)init 
{
    self = [super init];
    if (self != nil) {
        if (cases == nil) {
            // this dict can be defined as a class variable
            cases = [[NSMutableDictionary alloc] initWithCapacity:2];
            [cases setObject:[NSNumber numberWithInt:foo] forKey:@"foo"];
            [cases setObject:[NSNumber numberWithInt:bar] forKey:@"bar"];
        }
    }
    return self;
}

- (void) switchFooBar:(NSString *) param {
    switch([[cases objectForKey:param] intValue]) {
        case foo:
            NSLog(@"its foo");
            break;
        case bar:
            NSLog(@"its bar");
            break;
        default: 
            NSLog(@"its default");
            break;
    }
}
@end

似乎没问题,但是#define 使 foo 和 bar 像保留字一样,我不能在我的代码中使用。如果我用类常量替换定义常量,这个问题就解决了,因为在其他类中我必须在常量名之前使用 MyClassName。但是我怎样才能最小化这个简单任务的对象分配呢?有人对此有“更好的做法”吗?

编辑: 下面的代码是我想做的,但是获取枚举或#define的值有点不舒服。因为我创建了一个只有一个输入的应用程序,我可以在其中编写字符串以获取该哈希并返回到 xcode 并设置枚举的值。所以我的问题是我不能在运行时这样做,因为 switch case 语句的主要行为......或者如果我用 NSDictionary 方式这样做 - >与这个解决方案相比,它有很多开销。

#import "CaseDemo.h"

typedef enum {
    foo = 1033772579,
    bar = -907719821
} FooBar;

unsigned int APHash(NSString* s)
{
    const char* str = [s UTF8String];
    unsigned int len = [s length];    

    unsigned int hash = 0xAAAAAAAA;
    unsigned int i    = 0;

    for(i = 0; i < len; str++, i++)
    {
        hash ^= ((i & 1) == 0) ? (  (hash <<  7) ^ (*str) * (hash >> 3)) :
        (~((hash << 11) + ((*str) ^ (hash >> 5))));
    }

    return hash;
}

@implementation CaseDemo


- (void) switchFooBar:(NSString *) param {
    switch(APHash(param)) {
        case foo:
            NSLog(@"its foo");
            break;
        case bar:
            NSLog(@"its bar");
            break;
        default: 
            NSLog(@"its default");
            break;

    }
}

@end 

注意:散列函数可以在 common 命名空间的其他地方定义以在任何地方使用它,通常我为这类东西创建一个 Utils.h 或 Common.h。

注意2:在“真实单词”中,我们需要使用一些加密散列函数,但现在我使用了 Arash Partow 的算法来保持示例的简单性。

那么,我的最后一个问题:有没有办法用预处理器以某种方式评估这些值?我认为没有,但也许? :-)

类似:

// !!!!!! I know this code is not working, I don't want comments about "this is wrong" !!!!
// I want a solution to invoke method with preprocessor, or something like that. 
typedef enum {
        foo = APHash(@"foo"),
        bar = APHash(@"bar")
    } FooBar;

更新:我找到了一个“可能的解决方案”,但它似乎只适用于 g++ 4.6>。 generalized constant expressions 可以帮我做。但是我还在测试中……

【问题讨论】:

  • @define?你的意思是#define
  • @BoltClock:我更正了谢谢!
  • @epatel:我不创建基准,但我认为哈希查找比字符串比较便宜。我认为 NSMutableDictionary 使用哈希表,因为开发人员文档说:“NSDictionary 与其核心基金会对应物 CFDictionary Reference 是‘免费桥接’的”。一次哈希查找比对每个字符串进行比较便宜。现在我认为,使我的方法更好的唯一方法是,我只使用一个哈希函数在第一次使用类变量初始化时存储字符串值。我把这些类变量引用放到了具体的案例中。
  • @epatel:我会将上面的示例附加到我的问题中。
  • @epatel:更新,我不能这样做,因为 case 只适用于编译时常量,如 c。

标签: objective-c xcode switch-statement


【解决方案1】:

该技术是从生产代码中提取的,并在此示例中修改为使用颜色。前提是字符串带有来自某些外部提要的颜色的文本名称。此入站颜色名称与系统中已知的 Crayola 颜色名称相匹配。如果新颜色名称与任何已知的 Crayola 颜色名称字符串匹配,则返回与该 Crayola 颜色名称等效的 HTML 十六进制代码的数值。

首先使用http://www.unit-conversion.info/texttools/crc/ 并将所有已知的 Crayola 颜色名称放入其中以获得等效的数字。这些将在案例陈述中使用。然后将这些值放入一个用于清洁的枚举中(例如下面的LivingColors)。这些数字等同于实际的颜色名称字符串。

然后在运行时变量文本通过相同的函数,但在您的代码内部,以生成相同类型的数字常量。如果代码中的数字常量与静态生成的常量匹配,则它们表示的文本字符串完全相等。

内部代码函数是crc32(),位于zlib.h。这会根据输入的文本生成一个唯一编号,就像上面的网页转换器一样。然后,crc32() 中的唯一编号可用于通用 C 语言 switch() 语句,以匹配已知颜色,这些颜色已预先处理为枚举中的数字。

要使用本机系统函数 crc32() 生成 CRC32B 值,请在您的项目中包含 /usr/lib/libz.1.dylib 以进行链接。请务必在引用 crc32() 的源代码中包含或 #import &lt;zlib.h&gt;

NSString 上实现一个Objective C 类别,以使本机NSString 类理解crc32:htmlColor: 消息。

最后,读取/获取颜色名称到 NSString 对象中,然后将字符串发送到 htmlColor: 消息,它会切换到匹配“字符串”并返回 Crayola 颜色名称的 HTML 十六进制等效值.

#import <zlib.h>

#define typedefEnum( enumName )               typedef enum enumName enumName; enum enumName

/**
    @see Crayola Web Colors https://www.w3schools.com/colors/colors_crayola.asp
    @see CRC32B value generator for static case strings http://www.unit-conversion.info/texttools/crc/ or http://www.md5calc.com
*/

#define typedefEnum( enumName ) typedef enum enumName enumName; enum enumName

typedefEnum( LivingColors ) {
    kRedColor                   = 0xc22c196f,    // "Red" is 0xED0A3F in HTML
    kBlueberryColor             = 0xfbefa670,    // "Blueberry" is 0x4F86F7 in HTML
    kLightChromeGreenColor      = 0x44b77242,    // "Light Chrome Green" is 0xBEE64B in HTML
    kPermanentGeraniumLakeColor = 0xecc4f3e4,    // "Permanent Geranium Lake" is 0xE12C2C in HTML
    kIlluminatingEmeraldColor   = 0x4828d5f2,    // "Illuminating Emerald" is 0x319177 in HTML
    kWildWatermelonColor        = 0x1a17c629,    // "Wild Watermelon" is 0xFD5B78 in HTML
    kWashTheDogColor            = 0xea9fcbe6,    // "Wash the Dog" is 0xFED85D in HTML
    kNilColor                   = 0xDEADBEEF     // could use '0' but what fun is that?
};


// generates the CRC32B, same used to check each ethernet packet on the network you receive so it’s fast
- (NSUInteger) crc32 {
    NSUInteger theResult;

    theResult = (NSUInteger)crc32(  0L,
                                    (const unsigned char *) [self UTF8String],
                                    (short)self.length);

    return theResult;
}

/// @return the HTML hex value for a recognized color name string.
- (NSUInteger) htmlColor {
    NSUInteger      theResult               = 0x0;
    LivingColors    theColorInLivingColor   = kNilColor;

    theColorInLivingColor = (LivingColors) [self crc32];
     // return the HTML value for a known color by effectively switching on a string.
    switch ( theColorInLivingColor ) {

        case kRedColor : {
            theResult = 0xED0A3F;
        }
            break;

        case kBlueberryColor : {
            theResult = 0x4F86F7;
        }
            break;

        case kLightChromeGreenColor : {
            theResult = 0xBEE64B;
        }
            break;

        case kPermanentGeraniumLakeColor : {
            theResult = 0xE12C2C;
        }
            break;

        case kIlluminatingEmeraldColor : {
            theResult = 0x319177;
        }
            break;

        case kWildWatermelonColor : {
            theResult = 0xFD5B78;
        }
            break;

        case kWashTheDogColor : {
            theResult = 0xFED85D;
        }
            break;

        case kNilColor :
        default : {
            theResult = 0x0;
        }
            break;

    }

    return theResult;
}

对于示例,创建了一个 Objective C 类别,以添加现有 Cocoa 类 NSString 的两个方法,而不是对其进行子类化。

最终结果是NSString 对象似乎 能够以本机方式获取自身的 CRC32B 值(在此示例之外非常方便)并且基本上可以在颜色的 switch(可能来自用户、文本文件等的名称字符串,以比任何文本字符串比较更快地识别匹配。

快速、高效且可靠,这种方法可以轻松地适应任何类型的可变文本匹配到静态已知值文本。请记住,CRC32B 校验和由按位运算生成,C switch 语句使用在编译时优化的按位运算。请记住,这很快,因为 CRC32B 是高度优化的函数,用于检查您的 Mac/iPhone/iPad 接收的每个以太网数据包……即使您下载了 macOS Sierra 等数 GB 的文件。

【讨论】:

    【解决方案2】:
    NSString * extension = [fileName pathExtension];
    NSString * directory = nil;
    
    NSUInteger index = [@[@"txt",@"png",@"caf",@"mp4"] indexOfObjectPassingTest:^
                        BOOL(id obj, NSUInteger idx, BOOL *stop)
    {
        return [obj isEqualToString:extension];
    }];
    
    switch (index)
    {
        case 0:
            directory = @"texts/";
            break;
        case 1:
            directory = @"images/";
            break;
        case 2:
            directory = @"sounds/";
            break;
        case 3:
            directory = @"videos/";
            break;
        default:
            @throw [NSException exceptionWithName:@"unkonwnFileFormat"
                                           reason:[NSString stringWithFormat:@"zip file contain nknown file format: %@",fileName]
                                         userInfo:nil];
            break;
    }
    

    【讨论】:

      【解决方案3】:
      typedef enum {
          foo,
          bar
      } FooBar;
      
      - (void) switchFooBar:(NSString *) param {
          switch([[cases objectForKey:param] intValue]) {
              case foo:
                  NSLog(@"its foo");
                  break;
              case bar:
                  NSLog(@"its bar");
                  break;
              default: 
                  NSLog(@"its default");
                  break;
          }
      }
      

      【讨论】:

      • 是否有理由需要使用字符串作为 switch 方法的输入?如果您使用 FooBar 枚举,那么您完全避免使用字典。
      • 编写一个函数,将 JSON 字符串值转换为 Enum 类型。 -(FooBar)foobarFromJsonVal:(NSString*)val{ if([val isEqualToString:@"foo"]) return foo; else return bar; }
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多