Objective-C 切换使用对象?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/104339/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Objective-C switch using objects?
提问by craigb
I'm doing some Objective-C programming that involves parsing an NSXmlDocument and populating an objects properties from the result.
我正在做一些 Objective-C 编程,涉及解析 NSXmlDocument 并从结果填充对象属性。
First version looked like this:
第一个版本看起来像这样:
if([elementName compare:@"companyName"] == 0)
[character setCorporationName:currentElementText];
else if([elementName compare:@"corporationID"] == 0)
[character setCorporationID:currentElementText];
else if([elementName compare:@"name"] == 0)
...
But I don't like the if-else-if-elsepattern this produces. Looking at the switchstatement I see that i can only handle ints, charsetc and not objects... so is there a better implementation pattern I'm not aware of?
但我不喜欢if-else-if-else这样产生的模式。查看switch声明,我看到我只能处理ints, charsetc 而不是对象......那么有没有我不知道的更好的实现模式?
BTW I did actually come up with a better solution for setting the object's properties, but I want to know specifically about the if-elsevs switchpattern in Objective-C
顺便说一句,我确实想出了一个更好的解决方案来设置对象的属性,但我想特别了解Objective-C 中的if- elsevsswitch模式
采纳答案by Michael Buckley
I hope you'll all forgive me for going out on a limb here, but I would like to address the more general question of parsing XML documents in Cocoa without the need of if-else statements. The question as originally stated assigns the current element text to an instance variable of the character object. As jmah pointed out, this can be solved using key-value coding. However, in a more complex XML document this might not be possible. Consider for example the following.
我希望你们能原谅我在这里冒昧,但我想解决更一般的问题,即在 Cocoa 中解析 XML 文档而不需要 if-else 语句。最初陈述的问题将当前元素文本分配给字符对象的实例变量。正如 jmah 所指出的,这可以使用键值编码来解决。但是,在更复杂的 XML 文档中,这可能是不可能的。例如考虑以下情况。
<xmlroot>
<corporationID>
<stockSymbol>EXAM</stockSymbol>
<uuid>31337</uuid>
</corporationID>
<companyName>Example Inc.</companyName>
</xmlroot>
There are multiple approaches to dealing with this. Off of the top of my head, I can think of two using NSXMLDocument. The first uses NSXMLElement. It is fairly straightforward and does not involve the if-else issue at all. You simply get the root element and go through its named elements one by one.
有多种方法可以解决这个问题。在我的头顶上,我可以想到两个使用 NSXMLDocument 的方法。第一个使用 NSXMLElement。它相当简单,根本不涉及 if-else 问题。您只需获取根元素并一一查看其命名元素即可。
NSXMLElement* root = [xmlDocument rootElement];
// Assuming that we only have one of each element.
[character setCorperationName:[[[root elementsForName:@"companyName"] objectAtIndex:0] stringValue]];
NSXMLElement* corperationId = [root elementsForName:@"corporationID"];
[character setCorperationStockSymbol:[[[corperationId elementsForName:@"stockSymbol"] objectAtIndex:0] stringValue]];
[character setCorperationUUID:[[[corperationId elementsForName:@"uuid"] objectAtIndex:0] stringValue]];
The next one uses the more general NSXMLNode, walks through the tree, and directly uses the if-else structure.
下一个使用更通用的NSXMLNode,遍历树,直接使用if-else结构。
// The first line is the same as the last example, because NSXMLElement inherits from NSXMLNode
NSXMLNode* aNode = [xmlDocument rootElement];
while(aNode = [aNode nextNode]){
if([[aNode name] isEqualToString:@"companyName"]){
[character setCorperationName:[aNode stringValue]];
}else if([[aNode name] isEqualToString:@"corporationID"]){
NSXMLNode* correctParent = aNode;
while((aNode = [aNode nextNode]) == nil && [aNode parent != correctParent){
if([[aNode name] isEqualToString:@"stockSymbol"]){
[character setCorperationStockSymbol:[aNode stringValue]];
}else if([[aNode name] isEqualToString:@"uuid"]){
[character setCorperationUUID:[aNode stringValue]];
}
}
}
}
This is a good candidate for eliminating the if-else structure, but like the original problem, we can't simply use switch-case here. However, we can still eliminate if-else by using performSelector. The first step is to define the a method for each element.
这是消除 if-else 结构的一个很好的候选者,但与原始问题一样,我们不能在这里简单地使用 switch-case。但是,我们仍然可以通过使用 performSelector 来消除 if-else。第一步是为每个元素定义一个方法。
- (NSNode*)parse_companyName:(NSNode*)aNode
{
[character setCorperationName:[aNode stringValue]];
return aNode;
}
- (NSNode*)parse_corporationID:(NSNode*)aNode
{
NSXMLNode* correctParent = aNode;
while((aNode = [aNode nextNode]) == nil && [aNode parent != correctParent){
[self invokeMethodForNode:aNode prefix:@"parse_corporationID_"];
}
return [aNode previousNode];
}
- (NSNode*)parse_corporationID_stockSymbol:(NSNode*)aNode
{
[character setCorperationStockSymbol:[aNode stringValue]];
return aNode;
}
- (NSNode*)parse_corporationID_uuid:(NSNode*)aNode
{
[character setCorperationUUID:[aNode stringValue]];
return aNode;
}
The magic happens in the invokeMethodForNode:prefix: method. We generate the selector based on the name of the element, and perform that selector with aNode as the only parameter. Presto bango, we've eliminated the need for an if-else statement. Here's the code for that method.
魔法发生在 invokeMethodForNode:prefix: 方法中。我们根据元素的名称生成选择器,并使用 aNode 作为唯一参数执行该选择器。Presto Bango,我们已经消除了对 if-else 语句的需要。这是该方法的代码。
- (NSNode*)invokeMethodForNode:(NSNode*)aNode prefix:(NSString*)aPrefix
{
NSNode* ret = nil;
NSString* methodName = [NSString stringWithFormat:@"%@%@:", prefix, [aNode name]];
SEL selector = NSSelectorFromString(methodName);
if([self respondsToSelector:selector])
ret = [self performSelector:selector withObject:aNode];
return ret;
}
Now, instead of our larger if-else statement (the one that differentiated between companyName and corporationID), we can simply write one line of code
现在,我们可以简单地编写一行代码,而不是我们更大的 if-else 语句(区分 companyName 和 CorporationID 的语句)
NSXMLNode* aNode = [xmlDocument rootElement];
while(aNode = [aNode nextNode]){
aNode = [self invokeMethodForNode:aNode prefix:@"parse_"];
}
Now I apologize if I got any of this wrong, it's been a while since I've written anything with NSXMLDocument, it's late at night and I didn't actually test this code. So if you see anything wrong, please leave a comment or edit this answer.
现在如果我弄错了,我很抱歉,我已经有一段时间没有用 NSXMLDocument 写任何东西了,现在已经深夜了,我实际上没有测试这段代码。因此,如果您发现任何错误,请发表评论或编辑此答案。
However, I believe I have just shown how properly-named selectors can be used in Cocoa to completely eliminate if-else statements in cases like this. There are a few gotchas and corner cases. The performSelector: family of methods only takes 0, 1, or 2 argument methods whose arguments and return types are objects, so if the types of the arguments and return type are not objects, or if there are more than two arguments, then you would have to use an NSInvocation to invoke it. You have to make sure that the method names you generate aren't going to call other methods, especially if the target of the call is another object, and this particular method naming scheme won't work on elements with non-alphanumeric characters. You could get around that by escaping the XML element names in your method names somehow, or by building an NSDictionary using the method names as the keys and the selectors as the values. This can get pretty memory intensive and end up taking a longer time. performSelector dispatch like I described is pretty fast. For very large if-else statements, this method may even be faster than an if-else statement.
然而,我相信我刚刚展示了如何在 Cocoa 中使用正确命名的选择器来完全消除这种情况下的 if-else 语句。有一些陷阱和角落案例。performSelector: 方法族只接受 0、1 或 2 个参数方法,它们的参数和返回类型都是对象,所以如果参数的类型和返回类型不是对象,或者如果有两个以上的参数,那么你会必须使用 NSInvocation 来调用它。您必须确保您生成的方法名称不会调用其他方法,特别是如果调用的目标是另一个对象,并且这种特定的方法命名方案不适用于具有非字母数字字符的元素。您可以通过以某种方式转义方法名称中的 XML 元素名称来解决这个问题,或者通过使用方法名称作为键和选择器作为值来构建 NSDictionary。这可能会占用大量内存并最终花费更长的时间。像我描述的那样 performSelector 调度非常快。对于非常大的 if-else 语句,此方法甚至可能比 if-else 语句更快。
回答by jmah
You should take advantage of Key-Value Coding:
您应该利用键值编码:
[character setValue:currentElementText forKey:elementName];
If the data is untrusted, you might want to check that the key is valid:
如果数据不受信任,您可能需要检查密钥是否有效:
if (![validKeysCollection containsObject:elementName])
// Exception or error
回答by epatel
Dare I suggest using a macro?
我敢建议使用宏吗?
#define TEST( _name, _method ) \
if ([elementName isEqualToString:@ _name] ) \
[character _method:currentElementText]; else
#define ENDTEST { /* empty */ }
TEST( "companyName", setCorporationName )
TEST( "setCorporationID", setCorporationID )
TEST( "name", setName )
:
:
ENDTEST
回答by Wevah
If you want to use as little code as possible, and your element names and setters are all named so that if elementName is @"foo" then setter is setFoo:, you could do something like:
如果您想使用尽可能少的代码,并且您的元素名称和 setter 都已命名,以便如果 elementName 为 @"foo" 则 setter 为 setFoo:,您可以执行以下操作:
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@:", [elementName capitalizedString]]);
[character performSelector:selector withObject:currentElementText];
or possibly even:
甚至可能:
[character setValue:currentElementText forKey:elementName]; // KVC-style
Though these will of course be a bit slower than using a bunch of if statements.
尽管这些当然比使用一堆 if 语句要慢一些。
[Edit: The second option was already mentioned by someone; oops!]
[编辑:第二个选项已经有人提到了;哎呀!]
回答by Brad Larson
One way I've done this with NSStrings is by using an NSDictionary and enums. It may not be the most elegant, but I think it makes the code a little more readable. The following pseudocode is extracted from one of my projects:
我使用 NSStrings 完成此操作的一种方法是使用 NSDictionary 和枚举。它可能不是最优雅的,但我认为它使代码更具可读性。以下伪代码是从我的一个项目中提取的:
typedef enum { UNKNOWNRESIDUE, DEOXYADENINE, DEOXYCYTOSINE, DEOXYGUANINE, DEOXYTHYMINE } SLSResidueType;
static NSDictionary *pdbResidueLookupTable;
...
if (pdbResidueLookupTable == nil)
{
pdbResidueLookupTable = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInteger:DEOXYADENINE], @"DA",
[NSNumber numberWithInteger:DEOXYCYTOSINE], @"DC",
[NSNumber numberWithInteger:DEOXYGUANINE], @"DG",
[NSNumber numberWithInteger:DEOXYTHYMINE], @"DT",
nil];
}
SLSResidueType residueIdentifier = [[pdbResidueLookupTable objectForKey:residueType] intValue];
switch (residueIdentifier)
{
case DEOXYADENINE: do something; break;
case DEOXYCYTOSINE: do something; break;
case DEOXYGUANINE: do something; break;
case DEOXYTHYMINE: do something; break;
}
回答by Dennis Munsie
Posting this as a response to Wevah's answer above -- I would've edited, but I don't have high enough reputation yet:
将此作为对上述 Wevah 回答的回应发布 - 我会进行编辑,但我的声誉还不够高:
unfortunately the first method breaks for fields with more than one word in them -- like xPosition. capitalizedString will convert that to Xposition, which when combined with the format give you setXposition: . Definitely not what was wanted here. Here is what I'm using in my code:
不幸的是,第一种方法对于包含多个单词的字段会中断——比如 xPosition。capitalizedString 会将其转换为 Xposition,当与格式结合使用时,将为您提供 setXposition: 。绝对不是这里想要的。这是我在代码中使用的内容:
NSString *capName = [elementName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[elementName substringToIndex:1] uppercaseString]];
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@:", capName]);
Not as pretty as the first method, but it works.
不如第一种方法漂亮,但它有效。
回答by Lvsti
I have come up with a solution that uses blocks to create a switch-like structure for objects. There it goes:
我想出了一个解决方案,它使用块为对象创建类似开关的结构。它是这样的:
BOOL switch_object(id aObject, ...)
{
va_list args;
va_start(args, aObject);
id value = nil;
BOOL matchFound = NO;
while ( (value = va_arg(args,id)) )
{
void (^block)(void) = va_arg(args,id);
if ( [aObject isEqual:value] )
{
block();
matchFound = YES;
break;
}
}
va_end(args);
return matchFound;
}
As you can see, this is an oldschool C function with variable argument list. I pass the object to be tested in the first argument, followed by the case_value-case_block pairs. (Recall that Objective-C blocks are just objects.) The whileloop keeps extracting these pairs until the object value is matched or there are no cases left (see notes below).
如您所见,这是一个带有可变参数列表的老式 C 函数。我在第一个参数中传递要测试的对象,然后是 case_value-case_block 对。(回想一下,Objective-C 块只是对象。)while循环不断提取这些对,直到对象值匹配或没有剩余案例为止(请参阅下面的注释)。
Usage:
用法:
NSString* str = @"stuff";
switch_object(str,
@"blah", ^{
NSLog(@"blah");
},
@"foobar", ^{
NSLog(@"foobar");
},
@"stuff", ^{
NSLog(@"stuff");
},
@"poing", ^{
NSLog(@"poing");
},
nil); // <-- sentinel
// will print "stuff"
Notes:
笔记:
- this is a first approximation without any error checking
- the fact that the case handlers are blocks, requires additional care when it comes to visibility, scope and memory management of variables referenced from within
- if you forget the sentinel, you are doomed :P
- you can use the boolean return value to trigger a "default" case when none of the cases have been matched
- 这是没有任何错误检查的第一个近似值
- 案例处理程序是块的事实,当涉及到从内部引用的变量的可见性、范围和内存管理时,需要格外小心
- 如果你忘记了哨兵,你就注定要失败 :P
- 当没有匹配任何情况时,您可以使用布尔返回值来触发“默认”情况
回答by Kendall Helmstetter Gelner
Although there's not necessarily a better way to do something like that for one time use, why use "compare" when you can use "isEqualToString"? That would seem to be more performant since the comparison would halt at the first non-matching character, rather than going through the whole thing to calculate a valid comparison result (though come to think of it the comparison might be clear at the same point) - also though it would look a little cleaner because that call returns a BOOL.
虽然不一定有更好的方法来做一次这样的事情,但为什么在可以使用“isEqualToString”的情况下使用“比较”?这似乎更高效,因为比较会在第一个不匹配的字符处停止,而不是通过整个过程来计算有效的比较结果(尽管考虑一下,比较可能在同一点很清楚) - 虽然它看起来会更干净一些,因为该调用返回一个 BOOL。
if([elementName isEqualToString:@"companyName"] )
[character setCorporationName:currentElementText];
else if([elementName isEqualToString:@"corporationID"] )
[character setCorporationID:currentElementText];
else if([elementName isEqualToString:@"name"] )
回答by Kendall Helmstetter Gelner
There is actually a fairly simple way to deal with cascading if-else statements in a language like Objective-C. Yes, you can use subclassing and overriding, creating a group of subclasses that implement the same method differently, invoking the correct implementation at runtime using a common message. This works well if you wish to choose one of a few implementations, but it can result in a needless proliferation of subclasses if you have many small, slightly different implementations like you tend to have in long if-else or switch statements.
实际上有一种相当简单的方法来处理像 Objective-C 这样的语言中的级联 if-else 语句。是的,您可以使用子类化和覆盖,创建一组以不同方式实现相同方法的子类,在运行时使用公共消息调用正确的实现。如果您希望选择几个实现中的一个,这很有效,但是如果您有许多小的、略有不同的实现,就像您在长 if-else 或 switch 语句中所拥有的那样,它可能会导致子类的不必要的增殖。
Instead, factor out the body of each if/else-if clause into its own method, all in the same class. Name the messages that invoke them in a similar fashion. Now create an NSArray containing the selectors of those messages (obtained using @selector()). Coerce the string you were testing in the conditionals into a selector using NSSelectorFromString() (you may need to concatenate additional words or colons to it first depending on how you named those messages, and whether or not they take arguments). Now have self perform the selector using performSelector:.
相反,将每个 if/else-if 子句的主体分解为它自己的方法,所有这些都在同一个类中。以类似的方式命名调用它们的消息。现在创建一个包含这些消息的选择器的 NSArray(使用@selector() 获得)。使用 NSSelectorFromString() 将您在条件中测试的字符串强制转换为选择器(您可能需要先将其他单词或冒号连接到它上面,具体取决于您如何命名这些消息,以及它们是否带有参数)。现在让自己使用 performSelector: 执行选择器。
This approach has the downside that it can clutter-up the class with many new messages, but it's probably better to clutter-up a single class than the entire class hierarchy with new subclasses.
这种方法的缺点是它可以用许多新消息把类弄乱,但是用新的子类把整个类层次结构弄乱可能比把单个类弄乱可能更好。
回答by Palmin
The if-elseimplementation you have is the right way to do this, since switchwon't work with objects. Apart from maybe being a bit harder to read (which is subjective), there is no real downside in using if-elsestatements this way.
if-else您拥有的实现是执行此操作的正确方法,因为它switch不适用于对象。除了可能有点难以阅读(这是主观的)之外,以if-else这种方式使用语句并没有真正的缺点。

