我希望你们都原谅我在这里跑题了,但是我想解决在 Cocoa 中解析 XML 文档而不需要 if-else 语句的更一般的问题。最初陈述的问题将当前元素文本分配给字符对象的实例变量。正如 jmah 指出的,这可以使用键值编码来解决。但是,在更复杂的 XML 文档中,这可能是不可能的。例如,考虑以下内容。
<xmlroot>
<corporationID>
<stockSymbol>EXAM</stockSymbol>
<uuid>31337</uuid>
</corporationID>
<companyName>Example Inc.</companyName>
</xmlroot>
有多种方法可以处理这个问题。在我的脑海中,我可以想到两个使用 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]];
下一个使用更通用的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]];
}
}
}
}
这是消除 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;
}
魔法发生在 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;
}
现在,我们可以简单地编写一行代码,而不是我们更大的 if-else 语句(区分 companyName 和corporateID 的语句)
NSXMLNode* aNode = [xmlDocument rootElement];
while(aNode = [aNode nextNode]){
aNode = [self invokeMethodForNode:aNode prefix:@"parse_"];
}
现在,如果我有任何错误,我深表歉意,自从我用 NSXMLDocument 编写任何东西以来已经有一段时间了,现在已经很晚了,我实际上并没有测试这段代码。因此,如果您发现任何错误,请发表评论或编辑此答案。
但是,我相信我刚刚展示了如何在 Cocoa 中使用正确命名的选择器来完全消除这种情况下的 if-else 语句。有一些陷阱和极端情况。performSelector: 系列方法只接受 0、1 或 2 个参数方法,它们的参数和返回类型都是对象,所以如果参数和返回类型的类型不是对象,或者有两个以上的参数,那么你会必须使用 NSInvocation 来调用它。您必须确保您生成的方法名称不会调用其他方法,特别是如果调用的目标是另一个对象,并且此特定方法命名方案不适用于具有非字母数字字符的元素。您可以通过以某种方式转义方法名称中的 XML 元素名称来解决此问题,或者通过使用方法名称作为键和选择器作为值来构建一个 NSDictionary。这可能会占用大量内存并最终花费更长的时间。像我描述的 performSelector 调度非常快。对于非常大的 if-else 语句,此方法甚至可能比 if-else 语句更快。