Analizuj właściwość zapytania NSURL


82

Mam taki adres URL myApp://action/1?parameter=2&secondparameter=3

Wraz z zapytaniem o właściwość otrzymuję część pliku URL

parameter=2&secondparameter=3

Czy jest jakiś sposób łatwy do umieszczenia tego w a NSDictionarylub an Array?

Wielkie dzięki

Odpowiedzi:


54

Coś w tym stylu:

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

Uwaga : to jest przykładowy kod. Nie zarządza się wszystkimi przypadkami błędów.


2
Ten kod ulegnie awarii w przypadku źle sformułowanego zapytania. Zobacz bezpieczne rozwiązanie poniżej.
BadPirate

2
Ok, po prostu sprawdź rozmiar tablicy! Ten kod był tylko początkiem (jak zawsze na SO). Jeśli chcesz zachować zgodność ze wszystkimi adresami URL, masz więcej prac do traktowania ciągów znaków ucieczki, fragmentów (# część na końcu), ...
Benoît

Wprowadź zmianę dla siebie. Tylko w dół zagłosowano, aby zwrócić uwagę na problem z odpowiedzią. Po zakończeniu edycji będę mógł ją przywrócić.
BadPirate

1
Ten kod nie jest nawet bliski poprawności, z wyjątkiem wartości najprostszych przypadków bez znaków specjalnych.
Nick Snyder

Zamiast tego powinieneś użyć ostatniego i pierwszego obiektu: [params setObject:[elts lastObject] forKey:[elts firstObject]];jest bezpieczniejszy
Laszlo

148

Możesz użyć queryItemsw URLComponents.

Po uzyskaniu wartości tej właściwości klasa NSURLComponents analizuje ciąg zapytania i zwraca tablicę obiektów NSURLQueryItem, z których każdy reprezentuje pojedynczą parę klucz-wartość, w kolejności, w jakiej występują w oryginalnym ciągu zapytania.

Szybki

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)

Alternatywnie możesz dodać rozszerzenie do adresu URL, aby ułatwić sprawę.

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

Możesz wtedy uzyskać dostęp do parametru poprzez jego nazwę.

let url = "http://example.com?param1=value1&param2=param2"
print(url.queryParameters["param1"])

11
zapytanie od iOS7. queryItems od iOS8.
Onato

Dzięki za tę odpowiedź. Oprócz tego uważam, że następujący artykuł jest bardzo pomocny, aby dowiedzieć się więcej o NSURLComponents: nshipster.com/nsurl
xaphod

Świetna odpowiedź. Dzięki!
dpigera,

O dziwo, NSURLComponents nie działa, jeśli w adresie URL jest znak #. Podobnie jak http://example.com/abc#abc?param1=value1&param2=param2 queryItems będzie zerowe
Pedro

Dzieje się tak, ponieważ fragment (#abc) należy na końcu.
Onato

55

Miałem powód, aby napisać kilka rozszerzeń dla tego zachowania, które mogą się przydać. Najpierw nagłówek:

#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

Te metody rozszerzają NSString, NSURL i NSDictionary, aby umożliwić konwersję do iz ciągów składników zapytania i obiektów słownika zawierających wyniki.

Teraz powiązany kod .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

dwa problemy: 1) musisz zdekodować adres URL zarówno klucz, jak i wartość przed zapisaniem ich w słowniku i 2) zgodnie z regułami adresów URL możesz wielokrotnie określać ten sam klucz. IOW, powinien to być klucz odwzorowany na tablicę wartości, a nie na pojedynczą wartość.
Dave DeLong

Ok, po prostu dodaj sprawdzenie rozmiaru tablicy (czy to wymaga głosowania negatywnego na moją odpowiedź?)! Ten kod był tylko początkiem (jak zawsze na SO). Jeśli chcesz zachować zgodność ze wszystkimi adresami URL, masz więcej prac do traktowania ciągów znaków ucieczki, fragmentów (# część na końcu), ...
Benoît

Dave - Dobre punkty. Nie było to wymagane ze względu na moje potrzeby, ale ponieważ staram się udzielić odpowiedzi, która będzie działać dla wszystkich odwiedzających ten typowy problem, dołączyłem wspomnianą funkcjonalność ... Benoit - Zwracam tylko uwagę na problem z odpowiedzią, Przesłałem zmianę. Prawdopodobnie nie jest to najlepsza forma do zwrócenia uwagi na drobne problemy w innych dobrych odpowiedziach, powstrzymam się od tego w przyszłości. Nie mogę zmienić głosowania
negatywnego,

W stringByDecodingURLFormat typem „result” powinno być NSString *.
ThomasW,

Właśnie zauważyłem inny problem. Klucz i wartość są pomieszane, kluczem powinien być objectAtIndex: 0, a wartość objectAtIndex: 1.
ThomasW,

13

Spróbuj tego ;)!

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
Należy również zauważyć - poza problemem awarii zauważonym przez BadPirate - (1), że klucze / wartości słownika są odwrócone; (2) klucz / wartości są nadal zakodowane, więc chcesz zrobić stringByDecodingURLFormat
dpjanes Kwietnia

Naprawdę fajny sposób na zrobienie tego
Burf2000,

9

Wszystkie poprzednie posty nie kodują poprawnie adresu URL. Sugerowałbym następujące metody:

+(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)jest zbędny. Możesz bezpiecznie sprawdzić, if([query length] == 0)jak w przypadku query == nil, gdy zwróci wartość, 0jeśli spróbujesz wywołać na nim długość.
JRG-Developer

czy nie powinno to być użycie objectForKey, a nie valueeforkey?
slf

Niezły kod. Proponuję używać NSUTF8StringEncodingw splitQuerydo obsługi wszystkich zestawów znaków :) [string stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]Jest też nowszy sposób kodowania concatenateQuery.
William Denniss

Myślę, że dobrą praktyką jest zwrócenie niezmiennej instancji, więc może powinieneś zmienić ostatnią linię, aby zwracała [kopiowanie parametrów];
Glenn Howes

Dzięki. Dostosowałem kod zgodnie z twoimi komentarzami!
Kristian Kraljic

7

Oto rozszerzenie w szybkim:

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

Zgodnie z już bardzo czystą odpowiedzią Onato , napisałem rozszerzenie NSURL w Swift, w którym można uzyskać taki parametr zapytania:

np. URL zawiera parę param = some_value

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

Rozszerzenie wygląda następująco:

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

  }
}

To najlepsza odpowiedź! Dlaczego nie sprawdzili dokumentów API?
Jamin Zhang

5

Dla osób używających Bolts Framework możesz użyć:

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

Pamiętaj, aby zaimportować:

#import <Bolts/BFURL.h>

Jeśli zdarzyło Ci się mieć w swoim projekcie Facebook SDK , masz również Bolts . Facebook używa tej struktury jako zależności.


W Swift with Bolts:BFURL(url: url)?.inputQueryParameters?["myKey"] as? String
JAL

4

Preferowanym sposobem radzenia sobie z adresami URL jest teraz NSURLComponents. W szczególności queryItemswłaściwość, która zwraca wartość NSArrayz params.

Jeśli chcesz, aby parametry w a NSDictionary, oto metoda:

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

Uwaga: adresy URL mogą mieć powtarzające się parametry, ale słownik będzie zawierał tylko ostatnią wartość dowolnego zduplikowanego parametru. Jeśli jest to niepożądane, użyj queryItemstablicy bezpośrednio.


3

Swift 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"]

Używany jako rozszerzenie 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 [:]
            }
        }
    }

}

Przykład:

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

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

Należy pamiętać, że jeśli używasz OS X 10.10 lub iOS 8 (lub nowszego), prawdopodobnie lepiej jest użyć NSURLComponentsi queryItemswłaściwości i utworzyć słownik NSURLQueryItemsbezpośrednio z.

Oto rozwiązanie NSURLComponentsoparte na NSURLrozszerzeniu:

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

}

Przypisem do rozszerzenia NSURL jest to, że w języku Swift można nadać właściwości taką samą nazwę, jak istniejąca właściwość ciągu - query. Nie wiedziałem, dopóki tego nie wypróbowałem, ale polimorfizm w Swift pozwala ci różnić się tylko typem zwrotu. Więc jeśli rozszerzona właściwość NSURL public var query: [String:String]działa. Nie użyłem tego w przykładzie, ponieważ uważam to za trochę szalone, ale działa ...


2

Opublikowałem proste zajęcia wykonujące pracę w ramach MIT:

https://github.com/anegmawad/URLQueryToCocoa

Dzięki niemu możesz mieć w zapytaniu tablice i obiekty, które są zbierane i sklejane

Na przykład

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

stanie się:

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

Możesz o tym myśleć jako o odpowiedniku zapytania URL NSJSONSerializer.


wspaniała robota. Wydaje się, że jest to jedyna poprawna odpowiedź (ta, która bierze pod uwagę tablicę i dyktowanie wartości).
Anton Tropashko

2

Wygląda na to, że używasz go do przetwarzania danych przychodzących z innej aplikacji iOS. Jeśli tak, to właśnie tego używam w tym samym celu.

Pierwsze połączenie (np. W aplikacji zewnętrznej):

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

Metoda wyodrębnienia danych QueryString z NSURL i zapisania jako 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;
}

Stosowanie:

NSDictionary *result = [self getNSDictionaryFromQueryString:url];

0

Ta klasa jest dobrym rozwiązaniem do analizowania adresów URL.

plik .h

@interface URLParser : NSObject {
    NSArray *variables;
}

@property (nonatomic, retain) NSArray *variables;

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

@end

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

Hendrik napisał ładny przykład rozszerzenia w tym pytaniu, jednak musiałem go przepisać, aby nie używać żadnych metod bibliotecznych obiektywnego-c. NSArraySzybkie używanie nie jest właściwym podejściem.

To jest wynik, wszystko szybkie i trochę bezpieczniejsze. Przykładem użycia będzie mniej linii kodu w 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
        }
    }
}

Stosowanie:

let queryItem = url.queryItemForKey("myItem")

Lub bardziej szczegółowe użycie:

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

Spróbuj tego:

-(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

Dość kompaktowe podejście:

    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

Najbardziej niezawodne rozwiązanie, jeśli używasz adresu URL do przekazywania danych z aplikacji internetowej do telefonu i chcesz przekazywać tablice, liczby, ciągi, ...

JSON koduje twój obiekt w PHP

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

Wynik dekodowania JSON w iOS

NSData *data = [[[request URL] host] dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *packed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

1
to okropny pomysł. Adres URL ma limit długości, który można łatwo przekroczyć, jeśli serializujesz do niego JSON.
Michael Baldry

jego około 2000 znaków
Michael Baldry,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.