Розбір властивості запиту NSURL


82

У мене є така URL-адреса myApp://action/1?parameter=2&secondparameter=3

За запитом властивості я отримую наступну частину мого URL

parameter=2&secondparameter=3

Чи є якийсь спосіб легко помістити це в a NSDictionaryабо an Array?

Thx багато

Відповіді:


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-адресами, у вас є більше робіт щодо обробки рядок евакуації, фрагмента (# частина в кінці), ...
Бенуа

Помістіть для вас редагування. Тільки за голосування проголосували, щоб привернути вашу увагу до питання з відповіддю. Як тільки редагування пройде, я зможу відновити його.
BadPirate

1
Цей код навіть не близький до правильного, за винятком найпростіших значень регістру без спеціальних символів.
Нік Снайдер

Натомість слід використовувати останній і перший об’єкт: [params setObject:[elts lastObject] forKey:[elts firstObject]];його більш безпечний
Ласло

147

Ви можете використовувати queryItemsв URLComponents.

Отримавши значення цієї властивості, клас NSURLComponents аналізує рядок запиту та повертає масив об’єктів NSURLQueryItem, кожен з яких представляє одну пару ключ-значення, в тому порядку, в якому вони відображаються у вихідному рядку запиту.

Стрімкий

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

Дивно, але NSURLComponents не працює, якщо в URL-адресі є символ #. Подібно http://example.com/abc#abc?param1=value1&param2=param2 queryItems буде нульовим
Педро

Це тому, що фрагмент (#abc) належить в кінці.
Onato

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

дві проблеми: 1) вам потрібно декодувати URL як ключ, так і значення, перш ніж зберігати їх у словнику; 2) згідно з правилами URL-адрес, ви можете вказати один і той же ключ кілька разів. IOW, це повинен бути ключ, що відображається на масив значень, а не на одне значення.
Dave DeLong

Гаразд, ви просто додаєте перевірку розміру масиву (це вимагає голосування проти моєї відповіді?)! Цей код був лише початком (як завжди на SO). Якщо ви хочете бути сумісними з усіма URL-адресами, у вас є більше робіт для обробки рядок евакуації, фрагмента (# частина в кінці), ...
Бенуа

Дейв - хороші бали. Не вимагався для моїх потреб, але оскільки я намагаюся дати відповідь, яка буде працювати для всіх, хто відвідує цю загальну проблему, я включив згадану функціональність ... Бенуа - просто звертаючи вашу увагу на проблему своєю відповіддю, Я подав редагування. Можливо, це не найкраща форма, щоб використовувати вниз, щоб привернути увагу до незначних питань у інших добрих відповідях, я буду утримуватися від цього в майбутньому .. Не можу змінити голосування проти, поки не пройде редагування: /
BadPirate

У stringByDecodingURLFormat типом 'результату' повинен бути NSString *.
ThomasW,

Щойно помітив ще одне питання. Ключ і значення змішуються, ключем повинен бути objectAtIndex: 0, а значення objectAtIndex: 1.
ThomasW

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

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якщо ви спробуєте зателефонувати на нього.
JRG-розробник

чи не для цього слід використовувати objectForKey, а не valueforkey?
slf

Хороший код. Я пропоную використовувати NSUTF8StringEncodingin splitQueryдля підтримки всіх наборів символів :) Крім того, [string stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]це новий спосіб кодування для concatenateQuery.
Вільям Денніс,

Я вважаю, що це хороша практика повертати незмінний екземпляр, тому, можливо, вам слід змінити останній рядок, щоб повернути [параметри копіювати];
Glenn Howes

Дякую. Я адаптував код відповідно до ваших коментарів!
Крістіан Краліч

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

Відповідно до вже дуже чистої відповіді Onato, я написав розширення для 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, у вас також є болти . Facebook використовує цей фреймворк як залежність.


У Свіфті з болтами:BFURL(url: url)?.inputQueryParameters?["myKey"] as? String
JAL

4

Зараз найкращим способом роботи з URL-адресами NSURLComponents. Зокрема queryItemsвластивість, яка повертає NSArrayпараметр.

Якщо вам потрібні параметри в 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

Свіфт 2.1

Oneliner:

"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.


відмінна робота. Здається, це єдина правильна відповідь (та, яка враховує значення масиву та дикту).
Антон Тропашко

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

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

0

У цьому питанні Хендрік написав гарний приклад для розширення, проте мені довелося переписати його, щоб не використовувати жодних методів бібліотеки-c. Використання NSArrayв стриже неправильний підхід.

Це результат, швидкий і трохи безпечніший. Прикладом використання буде менше рядків коду з 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;
}

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
    }

-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.
Michael Baldry

його близько 2000 символів
Майкл Болдрі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.