string引数のstringWithFormatの変数引数のリスト

パラメータの数が可変である場合、 NSString を使用してクエリを作成する最も賢い方法は何ですか?

私はパラメータのリストでフィルタリングされた国のリストを返すWebサービスを持っているとしましょう。例えば:

NSString *continent = @"europe";
NSString *language  = @"german";
NSString *timeZone  = @"UTC+1";
// to be continued

したがって、私のクエリ文字列は次のようになります:

[NSString stringWithFormat:@"/getCountries?continent=%@&language=%@&timezone=%@",
    continent, language, timeZone];

私の反応には

オーストリア、ドイツ、スイス

3つのパラメータすべてに一致するためです。

現在、これらのパラメータの0:nがゼロになる可能性がある場合、このクエリを作成する最もスマートな方法(たとえば、 if のスパゲッティコード)を探しています。 (例えば * timeZone nil の場合、URLには timezone = nil を含めるべきではありません)結果は NSString >。

2
@LuizCarlosQuerinoFilhoもともと私は完全に無しの部分を削除する方法を探していましたが、あなたの提案はこの場合もうまくいくはずです。
追加された 著者 Chris,
たとえば、大陸がない場合は、クエリの中から削除するか、それを置いて空にします。 continent =&language ...
追加された 著者 Luiz Carlos Querino Filho,

5 答え

私はこの1つのことを行い、それを正しく行うオブジェクトを作成します。

One key thing you have to look out for is to make sure that you escape all your names and values properly. For example, if you pass in a value of “fred&wilma”, that shouldn't appear as “members=fred&wilma” in the URL—you'd need the & to be escaped (as %26).

あなたはあなたがそれをステープルとしてあなたは単に各文字列をエスケープすることができますが、それを行うことを忘れることはあまりにも簡単です。コード内で繰り返されるものは、あまりにも簡単に見逃されます。

これが私が目的とする主な理由です。そのオブジェクトは、文字列を一緒にステープルするだけでなく、適切にエスケープすることもあります。

後でキーの値を参照する必要はないと仮定します。あなたはURLを構築したいだけです。

まず、オブジェクトは非公開の @property 変数またはインスタンス変数のいずれかでpublic @ code {@property }ではなくNSMutableStringをプライベートに保持する必要があります。これは、初期化でこれを作成し、その全体の生涯にわたって保持する必要があります。

オブジェクトの指定された初期化子は、 initWithResourceURLString:のようなものでなければなりません。このメソッドは文字列に mutableCopy というメッセージを送り、その結果をインスタンス変数またはプライベートプロパティに代入します。 init は例外をスローする必要があります( false をアサートするのが最も簡単です)。

オブジェクトには、単一の unichar のインスタンス変数も必要です。 initWithResourceURLString: '?' に設定する必要があります。

オブジェクトは setQueryParameterWithName:toValue:などのメッセージに応答する必要があります。各引数は文字列でなければなりません。いずれかの文字列が nil の場合、メソッドは単純に戻ります。

このメソッドは各文字列を送信します。

利点:

  • エスケープ処理が正しく行われているかどうかを確認する場所が1つだけあります。その他のコードはすべて心配する必要はありません。
  • nil が正しく処理(無視)されることを確実にする場所が1つだけあります。その他のコードはすべて心配する必要はありません。
  • 注文は保存されます。辞書は引数の順序を変更することがあります。これは問題ではありません(サーバーは気にしないでください)。もしそうであれば、注文保存ソリューションが必要です。
  • このオブジェクトの単体テストを書くのは簡単でしょう。
  • 必要に応じて、このオブジェクトをさまざまな方向に変更できます。たとえば、ベースURLを変更せずにリセットしたり、ベースURLを別に覚えたりせずに、このオブジェクトを複数のクエリに再利用できるようにするためのサポートを追加できます。また、空の文字列を nil に任意のパラメータまたは一部のパラメータに置き換えるサポートを追加することもできます。クラスに加えた変更は、他のURLビルドジョブですぐに利用できます。
素晴らしい答え。いつものように!
追加された 著者 Rob Keniger,
私が喜んで受け入れるあなたの精巧な答えをありがとう。その周りに道はない。
追加された 著者 Chris,

個々のNSStringではなくNSDictionaryにパラメータを格納します。その後、辞書を歩いてあなたのparam文字列を構築します。この実装は、0〜N個のパラメータを処理します。ベースURLをパラメータ化することによって、APIで使用できるように汎用化することもできます。

NSMutableString *paramString = [NSMutableString stringWithString:@"/getCountries"];
NSUInteger paramCount = 0;
[params enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL *stop){
  [paramString appendString:(paramCount == 0) ? @"?" : @"&"];
  NSString *newParam = [[NSString stringWithFormat:@"%@=%@", key, [params objectForKey:key]] stringByAddingPercentEscapesUsingEncoding:NSStringUTF8Encoding];
  [paramString appendString:newParam];
  paramCount++;  
}];
2
追加された

三項演算子を使うことができます:

[NSString stringWithFormat:@"/getCountries?continent=%@&language=%@&timezone=%@", (continent ? continent : @""), (language ? language : @""), (timezone ? timezone : @"")];
2
追加された
そうです、空のパラメータに対しては三項が行います。私はもう少しエレガントなものを望んでいました。
追加された 著者 Chris,

@XJonesは良いスタートを持っていますが、もっと簡単にできると思います。

NSDictionary *params = ...;
NSMutableArray *pairs = [NSMutableArray array];
for (NSString *key in params) {
  NSString *value = [params objectForKey:key];
  NSString *pair = [NSString stringWithFormat:@"%@=%@", key, value];
  [pairs addObject:pair];
}
NSString *queryString = [pairs componentsJoinedByString:@"&"];
NSString *path = [NSString stringWithFormat:@"/getCountries?%@", queryString];

もちろん、 key value を適切にURLエンコードする必要があります。

1
追加された
@XJones十分に公正。私はちょうど int カウンタが不要であると思っていました。
追加された 著者 Dave DeLong,
マイナーポイント、私はこの方法でb/cそれをしなかった辞書を歩く必要があります。私の解決策は、キーと値のペアを1回だけ通過します。
追加された 著者 XJones,
@luizでは、 NSString componentsSeparatedByString:の対応も気に入っています。
追加された 著者 XJones,
@dave、ええ、私はあなたの答えを書いていましたが、O(2N)の解決策を決めました。 「componentsJoinedByString」実装の内部は、私の場合と同様のカウンタです。あなたが私が推測するように最適化する方法に依存します。私はカウンターが好きではありませんが、どこかに存在しなければなりません。
追加された 著者 XJones,
あなたの雇用主に気づいたばかりの@dave。私はあなたが知っているだろうと思う... :)
追加された 著者 XJones,
それは涼しかった、私は componentsJoinedByString メソッドを知らなかった!
追加された 著者 Luiz Carlos Querino Filho,

単にあなたのパラメータをNSDictionaryに投げ、それに基づいてURLを構築してください:

NSMutableDictionary * dict = [NSMutableDictionary dictionary];
if (continent) [dict setObject:continent forKey:@"continent"];
if (timeZone) [dict setObject:timeZone forKey:@"timeZone"];
NSMutableString * queryString = [NSMutableString stringWithString:@"/getCountries?"];

NSArray * allKeys = [dict allKeys];
for (NSUInteger i = 0; i < [allKeys count]; i++) {
    NSString * key = [allKeys objectAtIndex:i];
    NSString * value = [dict objectForKey:key];
    NSString * escaped = [value stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];

    [queryString appendFormat:@"%@=%@", key, escaped];
    if (i + 1 < [allKeys count]) {
         [queryString appendString:@"&"];
    }
}
1
追加された