14

NSXmlDocument の解析と結果からのオブジェクト プロパティの設定を含む Objective-C プログラミングを行っています。

最初のバージョンは次のようになりました。

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しか処理できないことがわかります...私が気付いていないより良い実装パターンはありますか?intschars

ところで、オブジェクトのプロパティを設定するためのより良い解決策を実際に思いつきましたが、Objective-C のif- elsevsswitchパターンについて具体的に知りたいです

4

14 に答える 14

13

Key-Value コーディングを利用する必要があります。

[character setValue:currentElementText forKey:elementName];

データが信頼できない場合は、キーが有効であることを確認できます。

if (![validKeysCollection containsObject:elementName])
    // Exception or error
于 2008-09-21T04:37:51.907 に答える
12

ここで手足を踏み出すことを許していただけることを願っていますが、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 ステートメントよりも高速になる場合があります。

于 2008-09-26T09:26:59.737 に答える
7

できるだけ少ないコードを使用したい場合で、要素名とセッターにすべて名前を付けて、elementName が @"foo" の場合にセッターが setFoo: になるようにするには、次のようにします。

SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@:", [elementName capitalizedString]]);

[character performSelector:selector withObject:currentElementText];

またはおそらく:

[character setValue:currentElementText forKey:elementName]; // KVC-style

もちろん、これらは一連の if ステートメントを使用するよりも少し遅くなりますが。

[編集: 2 番目のオプションは既に誰かによって言及されています。おっとっと!]

于 2008-09-26T21:35:18.383 に答える
7

あえてマクロの使用をお勧めしますか?

#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
于 2008-09-26T19:49:43.037 に答える
4

NSStrings でこれを行った 1 つの方法は、NSDictionary と列挙型を使用することです。最もエレガントではないかもしれませんが、コードが少し読みやすくなると思います。次の疑似コードは、私のプロジェクトの 1 つから抽出されます。

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;
}
于 2008-09-23T00:14:44.380 に答える
3

ブロックを使用してオブジェクトのスイッチのような構造を作成するソリューションを考え出しました。そこに行きます:

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;
}

ご覧のとおり、これは可変引数リストを持つ昔ながらの C 関数です。最初の引数でテストするオブジェクトを渡し、その後に case_value-case_block のペアを渡します。(Objective-C ブロックは単なるオブジェクトであることを思い出してください。)whileループは、オブジェクトの値が一致するか、残りのケースがなくなるまで、これらのペアを抽出し続けます (以下の注を参照)。

使用法:

NSString* str = @"stuff";
switch_object(str,
              @"blah", ^{
                  NSLog(@"blah");
              },
              @"foobar", ^{
                  NSLog(@"foobar");
              },
              @"stuff", ^{
                  NSLog(@"stuff");
              },
              @"poing", ^{
                  NSLog(@"poing");
              },
              nil);   // <-- sentinel

// will print "stuff"

ノート:

  • これは、エラー チェックを行わない最初の概算です。
  • ケースハンドラーがブロックであるという事実は、内部から参照される変数の可視性、スコープ、およびメモリ管理に関しては、追加の注意が必要です
  • 歩哨を忘れたら、あなたは運命にある :P
  • ブール値の戻り値を使用して、どのケースも一致しなかった場合に「デフォルト」のケースをトリガーできます
于 2012-04-12T13:16:27.633 に答える
3

上記の Wevah の回答への回答としてこれを投稿します。編集したはずですが、まだ十分な評判がありません。

残念ながら、最初のメソッドは、xPosition のように複数の単語が含まれるフィールドでは機能しません。capitalizedString はそれを Xposition に変換し、フォーマットと組み合わせると setXposition: になります。ここで望んでいたことは間違いありません。コードで使用しているものは次のとおりです。

NSString *capName = [elementName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[elementName substringToIndex:1] uppercaseString]];
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@:", capName]);

最初の方法ほどきれいではありませんが、機能します。

于 2009-08-01T03:47:27.103 に答える
3

1 回の使用でこのようなことを行うためのより良い方法があるとは限りませんが、「isEqualToString」を使用できるのに、なぜ「compare」を使用するのでしょうか? 有効な比較結果を計算するために全体を処理するのではなく、最初の一致しない文字で比較が停止するため、パフォーマンスが向上するように思われます (考えてみると、比較は同じ時点で明らかである可能性があります)。 - また、その呼び出しは BOOL を返すため、少しきれいに見えます。

if([elementName isEqualToString:@"companyName"] ) 
  [character setCorporationName:currentElementText]; 
else if([elementName isEqualToString:@"corporationID"] ) 
  [character setCorporationID:currentElementText]; 
else if([elementName isEqualToString:@"name"] ) 
于 2008-09-21T04:30:17.653 に答える
3

オブジェクトでは機能しないため、if-else実装はこれを行う正しい方法です。switch少し読みにくい (これは主観的なものです) ことを除けば、if-elseステートメントをこのように使用することに実際のマイナス面はありません。

于 2008-09-19T19:07:00.043 に答える
3

実際には、Objective-C のような言語でカスケード if-else ステートメントを処理するかなり簡単な方法があります。はい、サブクラス化とオーバーライドを使用して、同じメソッドを異なる方法で実装するサブクラスのグループを作成し、共通のメッセージを使用して実行時に正しい実装を呼び出すことができます。これは、少数の実装から 1 つを選択したい場合にはうまく機能しますが、長い if-else 文や switch 文にありがちな、わずかに異なる小さな実装が多数ある場合は、サブクラスが不必要に増殖する可能性があります。

代わりに、各 if/else-if 句の本体を、すべて同じクラス内の独自のメソッドに分解します。同様の方法でそれらを呼び出すメッセージに名前を付けます。次に、これらのメッセージのセレクター (@selector() を使用して取得) を含む NSArray を作成します。NSSelectorFromString() を使用して、条件でテストしていた文字列をセレクターに強制します (これらのメッセージの名前の付け方や、引数を取るかどうかによっては、最初に追加の単語またはコロンを連結する必要がある場合があります)。今度は、performSelector: を使用してセレクターを自己実行させます。

このアプローチには、多くの新しいメッセージでクラスが乱雑になる可能性があるという欠点がありますが、新しいサブクラスでクラス階層全体を乱雑にするよりも、おそらく単一のクラスを乱雑にする方がよいでしょう。

于 2008-09-27T08:00:28.147 に答える
2

if-else または switch ステートメントを排除するために推奨される最も一般的なリファクタリングは、ポリモーフィズムを導入することです ( http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.htmlを参照)。このような条件文を削除することは、それらが重複している場合に最も重要です。サンプルのような XML 解析の場合、本質的にデータをより自然な構造に移動しているため、条件を他の場所で複製する必要はありません。この場合、おそらく if-else または switch ステートメントで十分です。

于 2008-09-19T19:03:00.753 に答える
1

この場合、Bradleyが示唆するように、クラスを簡単にリファクタリングしてポリモーフィズムを導入できるかどうかはわかりません。これは、Cocoaネイティブクラスだからです。代わりに、Objective-Cでそれを行う方法は、クラスカテゴリを使用しelementNameCodeてNSStingにメソッドを追加することです。

   typedef enum { 
       companyName = 0,
       companyID,  
       ...,
       Unknown
    } ElementCode;

    @interface NSString (ElementNameCodeAdditions)
    - (ElementCode)elementNameCode; 
    @end

    @implementation NSString (ElementNameCodeAdditions)
    - (ElementCode)elementNameCode {
        if([self compare:@"companyName"]==0) {
            return companyName;
        } else if([self compare:@"companyID"]==0) {
            return companyID;
        } ... {

        }

        return Unknown;
    }
    @end

コードで、スイッチをオンにすることができます[elementName elementNameCode](enumメンバーの1つなどをテストするのを忘れた場合は、関連するコンパイラの警告が表示されます)。

Bradleyが指摘しているように、ロジックが1つの場所でのみ使用されている場合、これは価値がない可能性があります。

于 2008-09-20T23:00:21.143 に答える
1

この種のことを何度も繰り返す必要があるプロジェクトで行ったことは、チェックする文字列/オブジェクトを単純な整数値にマッピングする静的CFDictionaryを設定することです。次のようなコードになります。

static CFDictionaryRef  map = NULL;
int count = 3;
const void *keys[count] = { @"key1", @"key2", @"key3" };
const void *values[count] = { (uintptr_t)1, (uintptr_t)2, (uintptr_t)3 };

if (map == NULL)
    map = CFDictionaryCreate(NULL,keys,values,count,&kCFTypeDictionaryKeyCallBacks,NULL);


switch((uintptr_t)CFDictionaryGetValue(map,[node name]))
{
    case 1:
        // do something
        break;
    case 2:
        // do something else
        break;
    case 3:
        // this other thing too
        break;
}

Leopardのみを対象としている場合は、CFDictionaryの代わりにNSMapTableを使用できます。

于 2008-11-04T17:48:20.240 に答える
0

Lvsti と同様に、ブロックを使用してオブジェクトの切り替えパターンを実行しています。

n 個のフィルター ブロックを取り、オブジェクトに対して各フィルターを実行する、非常に単純なフィルター ブロック ベースのチェーンを作成しました。
各フィルターはオブジェクトを変更できますが、それを返す必要があります。何があっても。

NSObject+Functional.h

#import <Foundation/Foundation.h>
typedef id(^FilterBlock)(id element, NSUInteger idx, BOOL *stop);

@interface NSObject (Functional)
-(id)processByPerformingFilterBlocks:(NSArray *)filterBlocks;
@end

NSObject+Functional.m

@implementation NSObject (Functional)
-(id)processByPerformingFilterBlocks:(NSArray *)filterBlocks
{
    __block id blockSelf = self;
    [filterBlocks enumerateObjectsUsingBlock:^( id (^block)(id,NSUInteger idx, BOOL*) , NSUInteger idx, BOOL *stop) {
        blockSelf = block(blockSelf, idx, stop);
    }];

    return blockSelf;
}
@end

nこれで、さまざまなケースをテストするために FilterBlocks をセットアップできます。

FilterBlock caseYES = ^id(id element, NSUInteger idx, BOOL *breakAfter){ 
    if ([element isEqualToString:@"YES"]) { 
        NSLog(@"You did it");  
        *breakAfter = YES;
    } 
    return element;
};

FilterBlock caseNO  = ^id(id element, NSUInteger idx, BOOL *breakAfter){ 
    if ([element isEqualToString:@"NO"] ) { 
        NSLog(@"Nope");
        *breakAfter = YES;
    }
    return element;
};

ここで、テストしたいブロックをフィルター チェーンとして配列に貼り付けます。

NSArray *filters = @[caseYES, caseNO];

オブジェクトでそれを実行できます

id obj1 = @"YES";
id obj2 = @"NO";
[obj1 processByPerformingFilterBlocks:filters];
[obj2 processByPerformingFilterBlocks:filters];

このアプローチは、切り替えに使用できますが、ブロックが要素を編集して渡すことができるため、任意の (条件付き) フィルター チェーン アプリケーションにも使用できます。

于 2013-02-09T15:16:35.807 に答える