Я надеюсь, что вы все простите меня за то, что я здесь потренировался, но я хотел бы затронуть более общий вопрос синтаксического анализа 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. Тем не менее, мы все еще можем устранить if-else с помощью executeSelector. Первым шагом является определение метода для каждого элемента.
- (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 и corporationID) мы можем просто написать одну строку кода
NSXMLNode* aNode = [xmlDocument rootElement];
while(aNode = [aNode nextNode]){
aNode = [self invokeMethodForNode:aNode prefix:@"parse_"];
}
Теперь я прошу прощения, если я что-то понял неправильно, прошло много времени с тех пор, как я что-то написал с NSXMLDocument, поздно ночью, и я фактически не тестировал этот код. Поэтому, если вы видите что-то не так, пожалуйста, оставьте комментарий или отредактируйте этот ответ.
Тем не менее, я считаю, что я только что показал, как в Какао можно использовать селекторы с правильными именами, чтобы полностью исключить операторы if-else в подобных случаях. Есть несколько ошибок и угловых случаев. Семейство методов executeSelector: принимает только 0, 1 или 2 метода аргументов, аргументы и возвращаемые типы которых являются объектами, поэтому, если типы аргументов и возвращаемый тип не являются объектами или если имеется более двух аргументов, то вы должны должен использовать NSInvocation, чтобы вызвать его. Вы должны убедиться, что имена методов, которые вы генерируете, не будут вызывать другие методы, особенно если целью вызова является другой объект, и эта конкретная схема именования методов не будет работать с элементами с не алфавитно-цифровыми символами. Вы можете обойти это, экранируя имена элементов XML в именах ваших методов или создав NSDictionary, используя имена методов в качестве ключей и селекторы в качестве значений. Это может занять довольно много памяти и в конечном итоге займет больше времени. Как я описал, рассылка executeSelector выполняется довольно быстро. Для очень больших операторов if-else этот метод может быть даже быстрее, чем оператор if-else.