问题描述
我正在执行一些目标-C编程,其中涉及解析NSXMLDOcument并从结果中填充对象属性.
第一个版本看起来像这样:
if([elementName compare:@"companyName"] == 0) [character setCorporationName:currentElementText]; else if([elementName compare:@"corporationID"] == 0) [character setCorporationID:currentElementText]; else if([elementName compare:@"name"] == 0) ...
,但我不喜欢这会产生的if-else-if-else模式.查看switch语句,我看到我只能处理ints,chars等,而不是对象...所以我不知道更好的实现模式吗?
顺便说一句,我确实提出了一个更好的解决方案来设置对象的属性,但是我想专门了解Objective-C
中的if - else vs switch模式推荐答案
我希望你们所有人都可以原谅我在这里的肢体,但是我想解决在可可中解析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结构的好候选者,但是像原始问题一样,我们不能简单地在此处使用开关案例.但是,我们仍然可以通过使用PermorySelector消除IF-ELSE.第一步是为每个元素定义A方法.
- (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:前缀:方法中.我们根据元素的名称生成选择器,并以阳极为唯一的参数执行该选择器. 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和CorporationID之间有区别的语句)NSXMLNode* aNode = [xmlDocument rootElement]; while(aNode = [aNode nextNode]){ aNode = [self invokeMethodForNode:aNode prefix:@"parse_"]; }
现在,我深表歉意,如果我错了,那已经有一段时间了,因为我用nsxmldocument写了任何东西,它已经深夜了,我实际上没有测试此代码.因此,如果您发现任何错误,请发表评论或编辑此答案.
但是,我相信我刚刚展示了如何在可可中使用正确命名的选择器在这种情况下完全消除IF-ELSE语句.有一些陷阱和角案.绩效下:方法家族仅采用0、1或2个参数方法,其参数和返回类型是对象,因此,如果参数和返回类型的类型不是对象,或者如果有两个以上的参数,那么您将必须使用NSINEvocation调用它.您必须确保您生成的方法名称不会呼叫其他方法,尤其是如果调用目标是另一个对象,并且此特定的方法命名方案对具有非alphanumeric字符的元素不起作用.您可以通过以某种方式将XML元素名称逃脱,或者使用方法名称作为键和选择器作为值来构建nsdictionary.这可以使记忆力很强,最终需要更长的时间.像我描述的那样,PerformSelector调度非常快.对于非常大的IF-ELSE语句,此方法甚至可能比If-Else语句要快.
其他推荐答案
您应该利用键值编码:
[character setValue:currentElementText forKey:elementName];
如果数据不受信任,您可能需要检查键是否有效:
if (![validKeysCollection containsObject:elementName]) // Exception or error
其他推荐答案
我建议使用宏?
#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
问题描述
I'm doing some Objective-C programming that involves parsing an NSXmlDocument and populating an objects properties from the result.
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-else pattern this produces. Looking at the switch statement I see that i can only handle ints, chars etc and not objects... so is there a better implementation pattern I'm not aware of?
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-else vs switch pattern in Objective-C
推荐答案
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.
<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.
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.
// 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.
- (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.
- (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
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.
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.
其他推荐答案
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
其他推荐答案
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