Разобрать свойство запроса NSURL

82

У меня есть URL типа myApp://action/1?parameter=2&secondparameter=3

С запросом свойства я получаю следующую часть моего URL

parameter=2&secondparameter=3

Есть ли способ легко поместить это в NSDictionaryили в Array?

Большое спасибо

габак
источник

Ответы:

54

Что-то такое:

NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
for (NSString *param in [url componentsSeparatedByString:@"&"]) {
  NSArray *elts = [param componentsSeparatedByString:@"="];
  if([elts count] < 2) continue;
  [params setObject:[elts lastObject] forKey:[elts firstObject]];
}

Примечание . Это пример кода. Не все случаи ошибок устраняются.

Бенуа
источник
2
Этот код выйдет из строя в случае неверного запроса. См. Безопасное решение ниже.
BadPirate
2
Хорошо, вы просто добавляете проверку размера массива! Этот код был только началом (как всегда на SO). Если вы хотите соответствовать всем URL-адресам, у вас есть больше работ по обработке escape-строк, фрагментов (# часть в конце), ...
Бенуа
Внесите правку для вас. Только против проголосовали за то, чтобы привлечь ваше внимание к проблеме с ответом. Как только редактирование будет завершено, я смогу вернуть его.
BadPirate
1
Этот код даже не близок к правильному, за исключением простейших значений case без специальных символов.
Ник Снайдер
Вместо этого вы должны использовать последний и первый объект: [params setObject:[elts lastObject] forKey:[elts firstObject]];он более безопасен
Ласло
148

Вы можете использовать queryItemsв URLComponents.

Когда вы получаете значение этого свойства, класс NSURLComponents анализирует строку запроса и возвращает массив объектов NSURLQueryItem, каждый из которых представляет одну пару «ключ-значение», в том порядке, в котором они появляются в исходной строке запроса.

Swift

let url = "http://example.com?param1=value1&param2=param2"
let queryItems = URLComponents(string: url)?.queryItems
let param1 = queryItems?.filter({$0.name == "param1"}).first
print(param1?.value)

Кроме того, вы можете добавить расширение к URL-адресу, чтобы упростить задачу.

extension URL {
    var queryParameters: QueryParameters { return QueryParameters(url: self) }
}

class QueryParameters {
    let queryItems: [URLQueryItem]
    init(url: URL?) {
        queryItems = URLComponents(string: url?.absoluteString ?? "")?.queryItems ?? []
        print(queryItems)
    }
    subscript(name: String) -> String? {
        return queryItems.first(where: { $0.name == name })?.value
    }
}

Затем вы можете получить доступ к параметру по его имени.

let url = "http://example.com?param1=value1&param2=param2"
print(url.queryParameters["param1"])
Онато
источник
11
запрос с iOS7. queryItems начиная с iOS8.
Onato
Спасибо за этот ответ. В дополнение к этому, я нашел следующую статью очень полезной, чтобы узнать больше о NSURLComponents: nshipster.com/nsurl
xaphod
Отличный ответ. Благодаря!
dpigera 07
Как ни странно, NSURLComponents не работает, если в URL-адресе есть символ #. Like http://example.com/abc#abc?param1=value1&param2=param2 queryItems будет равен нулю
Педро
Это потому, что фрагмент (#abc) находится в конце.
Onato 05
55

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

#import <Foundation/Foundation.h>

@interface NSString (XQueryComponents)
- (NSString *)stringByDecodingURLFormat;
- (NSString *)stringByEncodingURLFormat;
- (NSMutableDictionary *)dictionaryFromQueryComponents;
@end

@interface NSURL (XQueryComponents)
- (NSMutableDictionary *)queryComponents;
@end

@interface NSDictionary (XQueryComponents)
- (NSString *)stringFromQueryComponents;
@end

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

Теперь связанный код .m:

#import "XQueryComponents.h"

@implementation NSString (XQueryComponents)
- (NSString *)stringByDecodingURLFormat
{
    NSString *result = [self stringByReplacingOccurrencesOfString:@"+" withString:@" "];
    result = [result stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    return result;
}

- (NSString *)stringByEncodingURLFormat
{
    NSString *result = [self stringByReplacingOccurrencesOfString:@" " withString:@"+"];
    result = [result stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    return result;
}

- (NSMutableDictionary *)dictionaryFromQueryComponents
{
    NSMutableDictionary *queryComponents = [NSMutableDictionary dictionary];
    for(NSString *keyValuePairString in [self componentsSeparatedByString:@"&"])
    {
        NSArray *keyValuePairArray = [keyValuePairString componentsSeparatedByString:@"="];
        if ([keyValuePairArray count] < 2) continue; // Verify that there is at least one key, and at least one value.  Ignore extra = signs
        NSString *key = [[keyValuePairArray objectAtIndex:0] stringByDecodingURLFormat];
        NSString *value = [[keyValuePairArray objectAtIndex:1] stringByDecodingURLFormat];
        NSMutableArray *results = [queryComponents objectForKey:key]; // URL spec says that multiple values are allowed per key
        if(!results) // First object
        {
            results = [NSMutableArray arrayWithCapacity:1];
            [queryComponents setObject:results forKey:key];
        }
        [results addObject:value];
    }
    return queryComponents;
}
@end

@implementation NSURL (XQueryComponents)
- (NSMutableDictionary *)queryComponents
{
    return [[self query] dictionaryFromQueryComponents];
}
@end

@implementation NSDictionary (XQueryComponents)
- (NSString *)stringFromQueryComponents
{
    NSString *result = nil;
    for(__strong NSString *key in [self allKeys])
    {
        key = [key stringByEncodingURLFormat];
        NSArray *allValues = [self objectForKey:key];
        if([allValues isKindOfClass:[NSArray class]])
            for(__strong NSString *value in allValues)
            {
                value = [[value description] stringByEncodingURLFormat];
                if(!result)
                    result = [NSString stringWithFormat:@"%@=%@",key,value];
                else 
                    result = [result stringByAppendingFormat:@"&%@=%@",key,value];
            }
        else {
            NSString *value = [[allValues description] stringByEncodingURLFormat];
            if(!result)
                result = [NSString stringWithFormat:@"%@=%@",key,value];
            else 
                result = [result stringByAppendingFormat:@"&%@=%@",key,value];
        }
    }
    return result;
}
@end
BadPirate
источник
две проблемы: 1) вам необходимо декодировать URL-адрес и ключ, и значение, прежде чем сохранять их в словаре, и 2) в соответствии с правилами URL-адресов вам разрешено указывать один и тот же ключ несколько раз. IOW, это должен быть ключ, сопоставленный с массивом значений, а не с одним значением.
Дэйв Делонг,
Хорошо, вы просто добавляете проверку размера массива (это требует голосования за мой ответ?)! Этот код был только началом (как всегда на SO). Если вы хотите соответствовать всем URL-адресам, у вас есть больше работ по обработке escape-строк, фрагментов (# часть в конце), ...
Бенуа
Дэйв - Хорошие замечания. Не требовалось для моих нужд, но поскольку я пытаюсь дать ответ, который будет работать для всех, кто посещает эту общую проблему, я включил упомянутую функциональность ... Бенуа - Просто обращаю ваше внимание на проблему своим ответом, Я отправил правку. Вероятно, это не лучшая форма для привлечения внимания к незначительным проблемам в хороших ответах, я воздержусь от этого в будущем .. Не могу изменить голосование против, пока не будет
внесено правка
В stringByDecodingURLFormat тип для 'результата' должен быть NSString *.
ThomasW 05
Только что заметил еще одну проблему. Ключ и значение смешиваются, ключом должно быть objectAtIndex: 0, а значением objectAtIndex: 1.
ThomasW 05
13

Попробуй это ;)!

NSString *query = @"parameter=2&secondparameter=3"; // replace this with [url query];
NSArray *components = [query componentsSeparatedByString:@"&"];
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
for (NSString *component in components) {
    NSArray *subcomponents = [component componentsSeparatedByString:@"="];
    [parameters setObject:[[subcomponents objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
                   forKey:[[subcomponents objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
}
Elslooo
источник
1
Также обратите внимание - помимо проблемы сбоя, отмеченной BadPirate - (1), ключи / значения словаря меняются местами; (2) ключи / значения по-прежнему закодированы, поэтому вы хотите использовать stringByDecodingURLFormat
dpjanes
Действительно хороший способ сделать это
Burf2000
9

Все предыдущие сообщения неправильно кодируют URL. Я бы предложил следующие методы:

+(NSString*)concatenateQuery:(NSDictionary*)parameters {
    if([parameters count]==0) return nil;
    NSMutableString* query = [NSMutableString string];
    for(NSString* parameter in [parameters allKeys])
        [query appendFormat:@"&%@=%@",[parameter stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet],[[parameters objectForKey:parameter] stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]];
    return [[query substringFromIndex:1] copy];
}
+(NSDictionary*)splitQuery:(NSString*)query {
    if([query length]==0) return nil;
    NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
    for(NSString* parameter in [query componentsSeparatedByString:@"&"]) {
        NSRange range = [parameter rangeOfString:@"="];
        if(range.location!=NSNotFound)
            [parameters setObject:[[parameter substringFromIndex:range.location+range.length] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:[[parameter substringToIndex:range.location] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
        else [parameters setObject:[[NSString alloc] init] forKey:[parameter stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    }
    return [parameters copy];
}
Кристиан Кралич
источник
if(!query||[query length] == 0)избыточно. Вы можете смело проверять, так if([query length] == 0)как в этом случае query == nilон вернет значение, 0если вы попытаетесь вызвать length.
JRG-Developer
не следует ли использовать objectForKey, а не valueforkey?
slf
Хороший код. Я предлагаю использовать NSUTF8StringEncodingin splitQueryдля поддержки всех кодировок :) Кроме того, [string stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]это новый способ кодирования для concatenateQuery.
Уильям Деннисс
Я думаю, что это хорошая практика - возвращать неизменяемый экземпляр, поэтому, возможно, вам следует изменить последнюю строку, чтобы вернуть [копирование параметров];
Гленн Хоуз
Благодарю. Я адаптировал код с учетом ваших комментариев!
Kristian Kraljic
7

Вот быстрое расширение:

extension NSURL{
        func queryParams() -> [String:AnyObject] {
            var info : [String:AnyObject] = [String:AnyObject]()
            if let queryString = self.query{
                for parameter in queryString.componentsSeparatedByString("&"){
                    let parts = parameter.componentsSeparatedByString("=")
                    if parts.count > 1{
                        let key = (parts[0] as String).stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
                        let value = (parts[1] as String).stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
                        if key != nil && value != nil{
                            info[key!] = value
                        }
                    }
                }
            }
            return info
        }
    }
мукаисси
источник
7

Согласно уже очень чистому ответу Онато, я написал расширение для NSURL в Swift, где вы можете получить такой параметр запроса:

например, URL-адрес содержит пару param = some_value

let queryItem = url.queryItemForKey("param")
let value = queryItem.value // would get String "someValue"

Расширение выглядит так:

extension NSURL {

  var allQueryItems: [NSURLQueryItem] {
      get {
          let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)!
          let allQueryItems = components.queryItems!
          return allQueryItems as [NSURLQueryItem]
      }
  }

  func queryItemForKey(key: String) -> NSURLQueryItem? {

      let predicate = NSPredicate(format: "name=%@", key)!
      return (allQueryItems as NSArray).filteredArrayUsingPredicate(predicate).first as? NSURLQueryItem

  }
}
Хендрик
источник
Это лучший ответ! Почему они не проверили документы api?
Jamin Zhang
5

Для тех, кто использует Bolts Framework, вы можете использовать:

NSDictionary *parameters = [BFURL URLWithURL:yourURL].inputQueryParameters;

Не забудьте импортировать:

#import <Bolts/BFURL.h>

Если в вашем проекте есть Facebook SDK , у вас также есть Bolts . Facebook использует эту структуру как зависимость.

Раффал
источник
В Swift with Bolts:BFURL(url: url)?.inputQueryParameters?["myKey"] as? String
JAL
4

Предпочтительный способ работы с URL-адресами - сейчас NSURLComponents. В частности, queryItemsсвойство, которое возвращаетNSArray из params.

Если вам нужны параметры в a NSDictionary, вот метод:

+(NSDictionary<NSString *, NSString *>*)queryParamsFromURL:(NSURL*)url
{
    NSURLComponents* urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];

    NSMutableDictionary<NSString *, NSString *>* queryParams = [NSMutableDictionary<NSString *, NSString *> new];
    for (NSURLQueryItem* queryItem in [urlComponents queryItems])
    {
        if (queryItem.value == nil)
        {
            continue;
        }
        [queryParams setObject:queryItem.value forKey:queryItem.name];
    }
    return queryParams;
}

Предупреждение: URL-адреса могут иметь повторяющиеся параметры, но словарь будет содержать только последнее значение любого повторяющегося параметра. Если это нежелательно, используйте queryItemsмассив напрямую.

Уильям Деннисс
источник
3

Swift 2.1

Один лайнер:

"p1=v1&p2=v2".componentsSeparatedByString("&").map {
    $0.componentsSeparatedByString("=") 
}.reduce([:]) {
    (var dict: [String:String], p) in
    dict[p[0]] = p[1]
    return dict
}

// ["p1": "v1", "p2": "v2"]

Используется как расширение на NSURL:

extension NSURL {

    /**
     * URL query string as dictionary. Empty dictionary if query string is nil.
     */
    public var queryValues : [String:String] {
        get {
            if let q = self.query {
                return q.componentsSeparatedByString("&").map {
                    $0.componentsSeparatedByString("=") 
                }.reduce([:]) {
                    (var dict: [String:String], p) in
                    dict[p[0]] = p[1]
                    return dict
                }
            } else {
                return [:]
            }
        }
    }

}

Пример:

let url = NSURL(string: "http://example.com?p1=v1&p2=v2")!
let queryDict = url.queryValues

// ["p1": "v1", "p2": "v2"]

Пожалуйста , обратите внимание, при использовании OS X 10.10 или IOS 8 (или более поздней версии), это, вероятно , лучше использовать NSURLComponentsи queryItemsсвойство и создать словарь из NSURLQueryItemsнепосредственно.

Вот на NSURLComponentsоснове NSURLрешения расширения:

extension NSURL {

    /// URL query string as a dictionary. Empty dictionary if query string is nil.
    public var queryValues : [String:String] {
        get {
            guard let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false) else {
                return [:]
            }

            guard let queryItems = components.queryItems else {
                return [:]
            }

            var result:[String:String] = [:]
            for q in queryItems {
                result[q.name] = q.value
            }
            return result
        }
    }

}

Сноска к расширению NSURL заключается в том, что на самом деле в Swift можно дать свойству то же имя, что и существующее строковое свойство - query. Я не знал, пока не попробовал, но полиморфизм в Swift позволяет отличаться только типом возвращаемого значения. Итак, если расширенное свойство NSURL - public var query: [String:String]это работает. Я не использовал это в примере, так как считаю это немного сумасшедшим, но это работает ...

Эрик Тьернлунд
источник
2

Я опубликовал простой класс, выполняющий эту работу в MIT:

https://github.com/anegmawad/URLQueryToCocoa

С его помощью вы можете иметь в запросе массивы и объекты, которые собираются и склеиваются.

Например

users[0][firstName]=Amin&users[0][lastName]=Negm&name=Devs&users[1][lastName]=Kienle&users[1][firstName]=Christian

станет:

@{
   name : @"Devs",
   users :
   @[
      @{
         firstName = @"Amin",
         lastName = @"Negm"
      },
      @{
         firstName = @"Christian",
         lastName = @"Kienle"
      }
   ]
 }

Вы можете думать об этом как о аналоге URL-запроса NSJSONSerializer.

Амин Негм-Авад
источник
превосходная работа. Кажется, это единственно правильный ответ (тот, который учитывает значения array и dict).
Антон Тропашко
2

Похоже, вы используете его для обработки входящих данных из другого приложения iOS. Если да, то это то, что я использую для той же цели.

Первоначальный вызов (например, во внешнем приложении):

UIApplication *application = [UIApplication sharedApplication];
NSURL *url = [NSURL URLWithString:@"myApp://action/1?parameter=2&secondparameter=3"];
if ([application canOpenURL:url]) {
    [application openURL:url];
    NSLog(@"myApp is installed");
} else {
    NSLog(@"myApp is not installed");
}

Метод извлечения данных QueryString из NSURL и сохранения как NSDictionary:

-(NSDictionary *) getNSDictionaryFromQueryString:(NSURL *)url {
   NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
   NSRange needle = [url.absoluteString rangeOfString:@"?" options:NSCaseInsensitiveSearch];
   NSString *data = nil;

   if(needle.location != NSNotFound) {
       NSUInteger start = needle.location + 1;
       NSUInteger end = [url.absoluteString length] - start;
       data = [url.absoluteString substringWithRange:NSMakeRange(start, end)];
   }

   for (NSString *param in [data componentsSeparatedByString:@"&"]) {
       NSArray *keyvalue = [param componentsSeparatedByString:@"="];
       if([keyvalue count] == 2){
           [result setObject:[keyvalue objectAtIndex:1] forKey:[keyvalue objectAtIndex:0]];
       }
   }

  return result;
}

Применение:

NSDictionary *result = [self getNSDictionaryFromQueryString:url];
Gillsoft AB
источник
0

Этот класс - хорошее решение для парсинга URL.

.h файл

@interface URLParser : NSObject {
    NSArray *variables;
}

@property (nonatomic, retain) NSArray *variables;

- (id)initWithURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;

@end

.m файл

#import "URLParser.h"

@implementation URLParser
@synthesize variables;

- (id) initWithURLString:(NSString *)url{
    self = [super init];
    if (self != nil) {
        NSString *string = url;
        NSScanner *scanner = [NSScanner scannerWithString:string];
        [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"&?"]];
        NSString *tempString;
        NSMutableArray *vars = [NSMutableArray new];
        [scanner scanUpToString:@"?" intoString:nil];       //ignore the beginning of the string and skip to the vars
        while ([scanner scanUpToString:@"&" intoString:&tempString]) {
            [vars addObject:[tempString copy]];
        }
        self.variables = vars;
    }
    return self;
}

- (NSString *)valueForVariable:(NSString *)varName {
    for (NSString *var in self.variables) {
        if ([var length] > [varName length]+1 && [[var substringWithRange:NSMakeRange(0, [varName length]+1)] isEqualToString:[varName stringByAppendingString:@"="]]) {
            NSString *varValue = [var substringFromIndex:[varName length]+1];
            return varValue;
        }
    }
    return nil;
}

@end
Ymutlu
источник
0

Хендрик написал хороший пример расширения в этом вопросе, однако мне пришлось переписать его, чтобы не использовать какие-либо методы библиотеки objective-c. Использование NSArrayв swift - неправильный подход.

Это результат, все быстро и немного безопаснее. Пример использования будет меньше строк кода с Swift 1.2.

public extension NSURL {
    /*
    Set an array with all the query items
    */
    var allQueryItems: [NSURLQueryItem] {
        get {
            let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)!
            if let allQueryItems = components.queryItems {
                return allQueryItems as [NSURLQueryItem]
            } else {
                return []
            }
        }
    }

    /**
    Get a query item form the URL query

    :param: key The parameter to fetch from the URL query

    :returns: `NSURLQueryItem` the query item
    */
    public func queryItemForKey(key: String) -> NSURLQueryItem? {
        let filteredArray = filter(allQueryItems) { $0.name == key }

        if filteredArray.count > 0 {
            return filteredArray.first
        } else {
            return nil
        }
    }
}

Применение:

let queryItem = url.queryItemForKey("myItem")

Или более подробное использование:

if let url = NSURL(string: "http://www.domain.com/?myItem=something") {
    if let queryItem = url.queryItemForKey("myItem") {
        if let value = queryItem.value {
            println("The value of 'myItem' is: \(value)")
        }
    }
}
Пол Пилен
источник
0

попробуй это:

-(NSDictionary *)getUrlParameters:(NSString *)url{
    NSArray *justParamsArr = [url componentsSeparatedByString:@"?"];
    url = [justParamsArr lastObject];
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    for (NSString *param in [url componentsSeparatedByString:@"&"]) {
        NSArray *elts = [param componentsSeparatedByString:@"="];
        if([elts count] < 2) continue;
        [params setObject:[elts lastObject] forKey:[elts firstObject]];
    }
    return params;
}
reza_khalafi
источник
0

Достаточно компактный подход:

    func stringParamsToDict(query: String) -> [String: String] {
        let params = query.components(separatedBy: "&").map {
            $0.components(separatedBy: "=")
        }.reduce(into: [String: String]()) { dict, pair in
            if pair.count == 2 {
                dict[pair[0]] = pair[1]
            }
        }
        return params
    }
Possen
источник
-5

Наиболее надежное решение, если вы используете URL-адрес для передачи данных из веб-приложения на телефон и хотите передавать массивы, числа, строки и т. Д.

JSON закодирует ваш объект в PHP

header("Location: myAppAction://".urlencode(json_encode($YOUROBJECT)));

И JSON декодирует результат в iOS

NSData *data = [[[request URL] host] dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *packed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
Уильям Энтрикен
источник
1
это ужасная идея. URL-адрес имеет ограничение по длине, которое можно легко превысить, если вы сериализуете в него JSON.
Майкл Болдри
это около 2000 символов
Майкл Болдри