Переменные списки аргументов в stringWithFormat для строки запроса - PullRequest
2 голосов
/ 25 ноября 2011

Каков самый умный способ построения запроса с NSString, когда число параметров является переменным?

Допустим, у меня есть веб-сервис, который возвращает список стран, отфильтрованных по списку параметров. Например:

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

Таким образом, моя строка запроса будет:

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

Мой ответ будет содержать

Австрия, Германия и Швейцария

потому что они соответствуют всем трем параметрам.

Теперь я ищу самый умный способ (например, не код спагетти if s) для построения этого запроса, когда 0: n этих параметров может быть нулем. (например, когда *timeZone равен nil, URL не должен содержать timezone=nil) Результат должен быть NSString.

Ответы [ 5 ]

5 голосов
/ 25 ноября 2011

Я бы сделал объект, который делает это одно и делает это правильно.

Одна ключевая вещь, на которую вы должны обратить внимание - это убедиться, что вы правильно экранировали все свои имена и значения. Например, если вы передадите значение «fred & wilma», оно не должно отображаться в URL как «members = fred & wilma» - вам нужно экранировать & (как %26).

Вы могли бы просто избегать каждой строки, когда сшиваете ее, но было бы слишком легко забыть сделать это. Все, что повторяется в коде, слишком легко пропустить.

Это главная причина, по которой я бы сделал объект для этого. Этот объект будет не только скреплять строки, но и избегать их соответствующим образом.

Я предполагаю, что вам не нужно искать значения ключей позже. Вы просто хотите создать URL.

Во-первых, объект должен содержать NSMutableString, в частном порядке (не публичный @property - либо личный @property, либо переменная экземпляра). Он должен создать это при инициализации и удерживать его всю жизнь.

Назначенный инициализатором объекта должен быть что-то вроде initWithResourceURLString:. Этот метод отправляет в строку сообщение mutableCopy и присваивает результат переменной экземпляра или частному свойству. init должен выдать исключение (проще всего, утверждая false).

Объект также должен иметь переменную экземпляра для одного unichar. initWithResourceURLString: следует установить на '?'.

Объект должен ответить на сообщение, такое как setQueryParameterWithName:toValue:. Каждый аргумент должен быть строкой. Если любая из этих строк nil, метод просто возвращает.

Этот метод отправляет каждую строку stringByAddingPercentEscapesUsingEncoding: сообщение , а затем отправляет изменяемой строке сообщение appendFormat: в следующем формате:

%C%@=%@

с аргументами, являющимися символом в переменной экземпляра unichar, именем экранированного параметра и экранированным значением. Затем он устанавливает символьную переменную на &.

Объект должен ответить на сообщение, такое как constructedURLString, возвращая копию (автоматически выпущенную, если вы используете MRC) изменяемой строки.

Затем вы бы использовали этот объект так:

builder = [[MyURLBuilder alloc] initWithResourceURLString:@"http://example.com/getCountries"]; //Add autorelease under MRC
[builder setQueryParameterName:@"continent" toValue:continent];
[builder setQueryParameterName:@"language" toValue:language];
[builder setQueryParameterName:@"timeZone" toValue:timeZone];
NSURL *finishedURL = [NSURL URLWithString:[builder constructedURLString]];

Преимущества:

  • У вас есть ровно одно место, чтобы убедиться, что выход выполняется правильно. Весь другой код не должен беспокоиться об этом.
  • У вас есть ровно одно место, чтобы убедиться, что nil обрабатывается (игнорируется) правильно. Весь другой код не должен беспокоиться об этом.
  • Заказ сохранен. Словарь может изменить порядок аргументов. Это не должно иметь значения (сервер не должен заботиться), но если это так, то вам нужно решение для сохранения порядка.
  • Было бы легко написать модульные тесты для этого объекта.
  • Если вы хотите, вы можете изменить этот объект в нескольких направлениях. Например, вы можете добавить поддержку для сброса URL составленного запроса без изменения или необходимости отдельно запоминать базовый URL, что позволяет повторно использовать этот объект для нескольких запросов. Вы также можете добавить поддержку замены пустой строки на nil для любого или только для некоторых параметров. Любые изменения, внесенные вами в класс, будут немедленно доступны для любых других ваших заданий по созданию URL.
2 голосов
/ 25 ноября 2011

Храните параметры в NSDictionary вместо отдельных строк NSS. Затем пройдитесь по словарю, чтобы построить строку параметров. Эта реализация будет обрабатывать от 0 до N параметров. Вы можете обобщить его для использования в API, также параметризовав базовый URL.

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 голосов
/ 25 ноября 2011

Вы можете использовать троичный оператор, тогда:

[NSString stringWithFormat:@"/getCountries?continent=%@&language=%@&timezone=%@", (continent ? continent : @""), (language ? language : @""), (timezone ? timezone : @"")];
1 голос
/ 25 ноября 2011

Просто добавьте ваши параметры в 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 голос
/ 25 ноября 2011

@ 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];

Конечно, вам нужно правильно кодировать URL key и value.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...