在 Objective-C 中使用字符串实现可读 switch case 的最佳方法是什么?
在其他动态语言中,如 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
注意: 哈希函数可以在公共命名空间中的其他位置定义,以便在任何地方使用它,通常我会为此类内容创建 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>仅有的。 广义常量表达式 可能可以做到这一点为我。但我还在测试...
In other dynamic languages like ruby, javascript etc. you can do simply this:
switch(someString) {
case "foo":
//do something;
break;
case "bar":
// do something else;
break;
default:
// do something by default;
}
In objective-c, because it's derived very colsely from c language, you can't do that. My best practice for this is:
#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
It's seems to be ok, but #define makes foo and bar like a reserved word, and I can't use in my code. If I replace define constants with class constants, this problem is fixed, because in other classes I must use MyClassName before the constant name. But how can I minimize the object allocation for this simple task? Someone have a "better practice" for this?
EDIT:
The code below is what I wanted to do, but it's a little bit unconfortable to get the values of the enum or #define. Because I created an application what have just an input where I can write the string to get that hash and go back to xcode and set the values for the enums. So my problem is I can't do that in runtime time, because of the main behavour of switch case statement... Or if I do that with that NSDictionary way -> its have a lot of overhead compared with this solution.
#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
NOTE: the hash function can defined elsewhere in common namespace to use it anywhere, typically I create a Utils.h or Common.h for this kind of stuff.
NOTE2: In "real word" we need to use some cryptographic hashing function, but now I used the algorithm by Arash Partow to keep the example simple.
So, my final question: Is there a way to evaluate these values with the preprocessor somehow? I think no, but maybe? :-)
Something like:
// !!!!!! 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;
UPDATE: I found a "maybe solution" but it seems to be work with g++ 4.6> only. generalized constant expressions may be do it for me. But I'm still testing...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
该技术是从生产代码中提取的,并经过修改以在本示例中使用颜色。前提是字符串带有来自外部源的颜色文本名称。此入站颜色名称与系统中已知的 Crayola 颜色名称相匹配。如果新颜色名称与任何已知的 Crayola 颜色名称字符串匹配,则返回与该 Crayola 颜色名称等效的 HTML 十六进制代码的数值。
首先使用 http://www.unit-conversion.info/texttools/crc/ 将所有已知的 Crayola 颜色名称放入其中,以获得对应的数字。这些将在 case 语句中使用。然后将这些值放入枚举中以确保清洁度(例如下面的
LivingColors
)。这些数字等同于实际的颜色名称字符串。然后在运行时,变量文本通过相同的函数(但在代码内部)生成相同类型的数字常量。如果代码中的数字常量与静态生成的常量匹配,则它们表示的文本字符串完全相同。
内部代码函数是
crc32()
,位于zlib.h
中。这会根据输入的文本生成一个唯一的编号,就像上面的网页转换器一样。然后,可以在通用 Cswitch()
语句中使用来自crc32()
的唯一数字,以与已预处理为枚举数字的已知颜色进行匹配。要使用本机系统函数
crc32()
生成 CRC32B 值,请在项目中包含/usr/lib/libz.1.dylib
进行链接。请务必在引用crc32()
的源代码中包含或#import
在
NSString
上实现 Objective C 类别使原生NSString
类理解crc32:
和htmlColor:
消息。最后,将颜色名称读取/获取到
NSString
对象中,然后向该字符串发送htmlColor:
消息,它会切换以匹配“字符串”并返回 HTML Crayola 颜色名称的十六进制等效值。对于示例,创建了一个 Objective C 类别来将两个方法添加到现有的 Cocoa 类
NSString
中,而不是对其进行子类化。最终结果是,
NSString
对象似乎能够本机获取自身的 CRC32B 值(在本示例之外非常方便),并且本质上可以switch(
)对可能来自用户、文本文件等的颜色名称字符串进行识别,比任何文本字符串比较更快地识别匹配。这种方法快速、高效且可靠,可以轻松适应与静态已知值文本匹配的任何类型的可变文本。请记住,CRC32B 校验和是通过按位运算生成的,而 C switch 语句使用在编译时优化的按位运算。请记住,这速度很快,因为 CRC32B 是高度优化的函数,用于检查 Mac/iPhone/iPad 接收的每个以太网数据包...即使您下载了 macOS Sierra 等多 GB 文件。
The technique is extracted from production code and modified to use colors for this example. The premise is that a string comes in with the text name of a color from some external feed. This inbound color name is matched against known Crayola color names in the system. If the new color name matches any known Crayola color name strings, the numeric value for HTML hex code equivalent of that Crayola color name is returned.
First use http://www.unit-conversion.info/texttools/crc/ and put all of your known Crayola color names through it to get numerical equivalents. These will be used in the case statements. Then put those values into an enumerated for cleanliness (e.g.
LivingColors
below). These numbers become equivalent to the actual color name string.Then at run time the variable text is put through the same function, but internal to your code, to generate the same kind of numeric constant. If the numeric constant from the code matches the statically generated constant, then the text strings that they represent are exactly equal.
The internal code function is
crc32()
found inzlib.h
. This generates a unique number based upon the text put through it just like the web page converter above. The unique number fromcrc32()
can then be used in a common Cswitch()
statement to match against the known colors which were pre-processed into numbers into the enumerated.To use the native system function
crc32()
to generate CRC32B values, include the/usr/lib/libz.1.dylib
in your project for linking. Be sure to include or#import <zlib.h>
in your source that referencescrc32()
Implement an Objective C category on
NSString
to make the nativeNSString
class understand thecrc32:
andhtmlColor:
messages.Finally, read/get the name of the color into an
NSString
object, then send the string thehtmlColor:
message, it switches to match the 'strings' and returns the HTML hex equivalent value for a Crayola color name.For the example an Objective C Category was made to add the two methods the existing Cocoa class
NSString
, rather than subclass it.The end result is that an
NSString
object appears to have the ability to natively get a CRC32B value of itself (very handy beyond this example) and can essentiallyswitch(
) on the color’s name string that possibly came in from the user, a text file, etc. to identify a match much faster than any text string comparison can occur.Fast, efficient, and reliable, this approach can easily be adapted to any kind of variable text matching to static known value text. Bear in mind that CRC32B checksums are generated by bitwise operations and the C switch statement use bitwise operations optimized at compile time. Remember this is speedy because CRC32B is the highly optimized function used to check each ethernet packet your Mac/iPhone/iPad receives... even when you download multi-gigabyte files like macOS Sierra.