ここで手足を踏み出すことを許していただけることを願っていますが、if-else ステートメントを必要とせずに Cocoa で XML 文書を構文解析するという、より一般的な問題に取り組みたいと思います。最初に述べた質問は、現在の要素テキストを文字オブジェクトのインスタンス変数に割り当てます。jmah が指摘したように、これはキー値コーディングを使用して解決できます。ただし、より複雑な XML ドキュメントでは、これが不可能な場合があります。たとえば、次のことを考えてみましょう。
<xmlroot>
<corporationID>
<stockSymbol>EXAM</stockSymbol>
<uuid>31337</uuid>
</corporationID>
<companyName>Example Inc.</companyName>
</xmlroot>
これに対処するには、複数のアプローチがあります。頭のてっぺんから、NSXMLDocument を使用して 2 つのことを考えることができます。1 つ目は NSXMLElement を使用します。これは非常に簡単で、if-else の問題はまったくありません。ルート要素を取得し、その名前付き要素を 1 つずつ調べます。
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 を排除することはできます。最初のステップは、各要素の 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:prefix: メソッドで発生します。要素の名前に基づいてセレクターを生成し、唯一のパラメーターとして aNode を使用してそのセレクターを実行します。これで、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 を区別するステートメント) の代わりに、1 行のコードを書くだけで済みます。
NSXMLNode* aNode = [xmlDocument rootElement];
while(aNode = [aNode nextNode]){
aNode = [self invokeMethodForNode:aNode prefix:@"parse_"];
}
NSXMLDocument を使用して何かを書いてからしばらく経ちましたが、夜遅く、実際にこのコードをテストしていませんでした。何か問題がある場合は、コメントを残すか、この回答を編集してください。
しかし、Cocoa で適切な名前のセレクターを使用して、このような場合に if-else ステートメントを完全に排除する方法を示したと思います。いくつかの落とし穴とコーナーケースがあります。performSelector: ファミリのメソッドは、引数と戻り値の型がオブジェクトである 0、1、または 2 つの引数メソッドのみを取るため、引数と戻り値の型がオブジェクトでない場合、または 2 つ以上の引数がある場合は、 NSInvocation を使用して呼び出す必要があります。生成するメソッド名が他のメソッドを呼び出さないことを確認する必要があります。特に、呼び出しのターゲットが別のオブジェクトである場合、この特定のメソッド命名スキームは、英数字以外の文字を含む要素では機能しません。メソッド名の XML 要素名を何らかの方法でエスケープすることで、これを回避できます。または、メソッド名をキーとして使用し、セレクターを値として使用して NSDictionary を構築します。これはかなりメモリを集中的に使用し、最終的にはより長い時間がかかる可能性があります。私が説明したような performSelector ディスパッチはかなり高速です。非常に大きな if-else ステートメントの場合、このメソッドは if-else ステートメントよりも高速になる場合があります。