Creazione di parametri di query URL da oggetti NSDictionary in ObjectiveC


90

Con tutti gli oggetti di gestione degli URL che si trovano nelle librerie Cocoa standard (NSURL, NSMutableURL, NSMutableURLRequest, ecc.), So di dover trascurare un modo semplice per comporre programmaticamente una richiesta GET.

Attualmente sto aggiungendo manualmente "?" seguito da coppie nome valore unite da "&", ma tutte le mie coppie nome e valore devono essere codificate manualmente in modo che NSMutableURLRequest non fallisca completamente quando tenta di connettersi all'URL.

Questo sembra qualcosa per cui dovrei essere in grado di utilizzare un'API preconfezionata ... c'è qualcosa pronto per aggiungere un NSDictionary di parametri di query a un NSURL? C'è un altro modo in cui dovrei affrontarlo?


Tante risposte! Tanti voti su domande e risposte! E ancora la sua risposta non è contrassegnata. Woah!
BangOperator

Risposte:


132

Introdotto in iOS8 e OS X 10.10 è NSURLQueryItem, che può essere utilizzato per creare query. Dai documenti su NSURLQueryItem :

Un oggetto NSURLQueryItem rappresenta una singola coppia nome / valore per un elemento nella parte di query di un URL. Si utilizzano elementi di query con la proprietà queryItems di un oggetto NSURLComponents.

Per crearne uno usa l'inizializzatore designato queryItemWithName:value:e poi aggiungili a NSURLComponentsper generare un file NSURL. Per esempio:

NSURLComponents *components = [NSURLComponents componentsWithString:@"http://stackoverflow.com"];
NSURLQueryItem *search = [NSURLQueryItem queryItemWithName:@"q" value:@"ios"];
NSURLQueryItem *count = [NSURLQueryItem queryItemWithName:@"count" value:@"10"];
components.queryItems = @[ search, count ];
NSURL *url = components.URL; // http://stackoverflow.com?q=ios&count=10

Si noti che il punto interrogativo e la e commerciale vengono gestiti automaticamente. Creare un NSURLda un dizionario di parametri è semplice come:

NSDictionary *queryDictionary = @{ @"q": @"ios", @"count": @"10" };
NSMutableArray *queryItems = [NSMutableArray array];
for (NSString *key in queryDictionary) {
    [queryItems addObject:[NSURLQueryItem queryItemWithName:key value:queryDictionary[key]]];
}
components.queryItems = queryItems;

Ho anche scritto un post sul blog su come creare URL con NSURLComponentse NSURLQueryItems.


2
cosa succede se il dizionario è annidato?
hariszaman

1
@hariszaman se stai inviando un dizionario annidato tramite l'URL, dovresti probabilmente pensare di inviarlo tramite il corpo POST.
Joe Masilotti

2
solo per notare, il valore può essere solo di tipo NSString
igrrik

Voglio solo sottolineare un punto qui: il valore per un NSURLQueryItem può essere solo una stringa. In caso contrario, potrebbe verificarsi un arresto anomalo!
Pulkit Sharma

47

Puoi creare una categoria per NSDictionaryfarlo - non c'è un modo standard nella libreria Cocoa che io possa trovare. Il codice che uso è simile a questo:

// file "NSDictionary+UrlEncoding.h"
#import <cocoa/cocoa.h>

@interface NSDictionary (UrlEncoding)

-(NSString*) urlEncodedString;

@end

con questa implementazione:

// file "NSDictionary+UrlEncoding.m"
#import "NSDictionary+UrlEncoding.h"

// helper function: get the string form of any object
static NSString *toString(id object) {
  return [NSString stringWithFormat: @"%@", object];
}

// helper function: get the url encoded string form of any object
static NSString *urlEncode(id object) {
  NSString *string = toString(object);
  return [string stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
}

@implementation NSDictionary (UrlEncoding)

-(NSString*) urlEncodedString {
  NSMutableArray *parts = [NSMutableArray array];
  for (id key in self) {
    id value = [self objectForKey: key];
    NSString *part = [NSString stringWithFormat: @"%@=%@", urlEncode(key), urlEncode(value)];
    [parts addObject: part];
  }
  return [parts componentsJoinedByString: @"&"];
}

@end

Penso che il codice sia abbastanza semplice, ma ne discuto più in dettaglio su http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html .


16
Non credo che funzioni. In particolare, stai usando stringByAddingPercentEscapesUsingEncoding, che non esegue l'escape della e commerciale, così come altri caratteri che devono essere sottoposti a escape nei parametri di query come specificato in RFC 3986 . Considera invece di guardare il codice di escape di Google Toolbox per Mac .
Dave Peck

Questo funziona per una corretta Escaping: stackoverflow.com/questions/9187316/...
JoeCortopassi

30

Volevo usare la risposta di Chris, ma non era scritta per il conteggio automatico dei riferimenti (ARC), quindi l'ho aggiornata. Ho pensato di incollare la mia soluzione nel caso in cui qualcun altro avesse lo stesso problema. Nota: sostituire selfcon il nome dell'istanza o della classe dove appropriato.

+(NSString*)urlEscapeString:(NSString *)unencodedString 
{
    CFStringRef originalStringRef = (__bridge_retained CFStringRef)unencodedString;
    NSString *s = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,originalStringRef, NULL, (CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ", kCFStringEncodingUTF8);
    CFRelease(originalStringRef);
    return s;
}


+(NSString*)addQueryStringToUrlString:(NSString *)urlString withDictionary:(NSDictionary *)dictionary
{
    NSMutableString *urlWithQuerystring = [[NSMutableString alloc] initWithString:urlString];

    for (id key in dictionary) {
        NSString *keyString = [key description];
        NSString *valueString = [[dictionary objectForKey:key] description];

        if ([urlWithQuerystring rangeOfString:@"?"].location == NSNotFound) {
            [urlWithQuerystring appendFormat:@"?%@=%@", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
        } else {
            [urlWithQuerystring appendFormat:@"&%@=%@", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
        }
    }
    return urlWithQuerystring;
}

Mi piace questo codice, l'unica cosa da aggiungere è che la codifica sia sicura per tutte le chiavi / valori.
Pellet

22

Le altre risposte funzionano alla grande se i valori sono stringhe, tuttavia se i valori sono dizionari o array, questo codice lo gestirà.

È importante notare che non esiste un modo standard per passare un array / dizionario tramite la stringa di query ma PHP gestisce questo output perfettamente

-(NSString *)serializeParams:(NSDictionary *)params {
    /*

     Convert an NSDictionary to a query string

     */

    NSMutableArray* pairs = [NSMutableArray array];
    for (NSString* key in [params keyEnumerator]) {
        id value = [params objectForKey:key];
        if ([value isKindOfClass:[NSDictionary class]]) {
            for (NSString *subKey in value) {
                NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                                              (CFStringRef)[value objectForKey:subKey],
                                                                                              NULL,
                                                                                              (CFStringRef)@"!*'();:@&=+$,/?%#[]",
                                                                                              kCFStringEncodingUTF8);
                [pairs addObject:[NSString stringWithFormat:@"%@[%@]=%@", key, subKey, escaped_value]];
            }
        } else if ([value isKindOfClass:[NSArray class]]) {
            for (NSString *subValue in value) {
                NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                                              (CFStringRef)subValue,
                                                                                              NULL,
                                                                                              (CFStringRef)@"!*'();:@&=+$,/?%#[]",
                                                                                              kCFStringEncodingUTF8);
                [pairs addObject:[NSString stringWithFormat:@"%@[]=%@", key, escaped_value]];
            }
        } else {
            NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                                          (CFStringRef)[params objectForKey:key],
                                                                                          NULL,
                                                                                          (CFStringRef)@"!*'();:@&=+$,/?%#[]",
                                                                                          kCFStringEncodingUTF8);
            [pairs addObject:[NSString stringWithFormat:@"%@=%@", key, escaped_value]];
            [escaped_value release];
        }
    }
    return [pairs componentsJoinedByString:@"&"];
}

Esempi

[foo] => bar
[translations] => 
        {
            [one] => uno
            [two] => dos
            [three] => tres
        }

foo = bar e traduzioni [uno] = uno e traduzioni [due] = dos e traduzioni [tre] = tres

[foo] => bar
[translations] => 
        {
            uno
            dos
            tres
        }

foo = bar e traduzioni [] = uno e traduzioni [] = dos e traduzioni [] = tres


1
Per l'ultimo caso "else", ho aggiunto una chiamata alla descrizione in modo che fornisca stringhe per NSNumbers piuttosto che barf su una chiamata a length: '(CFStringRef) [[params objectForKey: key] description ]' Grazie!
JohnQ

Bella risposta. Tuttavia dovresti sostituire le CFURLCreateStringByAddingPercentEscapeschiamate constringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR

8

Ho modificato e convertito in risposta ARC da AlBeebe

- (NSString *)serializeParams:(NSDictionary *)params {
    NSMutableArray *pairs = NSMutableArray.array;
    for (NSString *key in params.keyEnumerator) {
        id value = params[key];
        if ([value isKindOfClass:[NSDictionary class]])
            for (NSString *subKey in value)
                [pairs addObject:[NSString stringWithFormat:@"%@[%@]=%@", key, subKey, [self escapeValueForURLParameter:[value objectForKey:subKey]]]];

        else if ([value isKindOfClass:[NSArray class]])
            for (NSString *subValue in value)
            [pairs addObject:[NSString stringWithFormat:@"%@[]=%@", key, [self escapeValueForURLParameter:subValue]]];

        else
            [pairs addObject:[NSString stringWithFormat:@"%@=%@", key, [self escapeValueForURLParameter:value]]];

}
return [pairs componentsJoinedByString:@"&"];

}

- (NSString *)escapeValueForURLParameter:(NSString *)valueToEscape {
     return (__bridge_transfer NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) valueToEscape,
             NULL, (CFStringRef) @"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8);
}

2
Questo ha funzionato benissimo per me, ma ho cambiato l'ultima riga per includere un? prima del primo parametro:[NSString stringWithFormat:@"?%@",[pairs componentsJoinedByString:@"&"]];
BFar

1
Penso che dovresti sostituire le CFURLCreateStringByAddingPercentEscapeschiamatestringByAddingPercentEncodingWithAllowedCharacters:[NSCharac‌​terSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR

5

Se stai già usando AFNetworking (come nel mio caso), puoi usare la sua classe AFHTTPRequestSerializerper creare il file NSURLRequest.

[[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:@"YOUR_URL" parameters:@{PARAMS} error:nil];

Nel caso in cui richiedi solo l'URL per il tuo lavoro, usa NSURLRequest.URL.


3

Ecco un semplice esempio in Swift (iOS8 +):

private let kSNStockInfoFetchRequestPath: String = "http://dev.markitondemand.com/Api/v2/Quote/json"

private func SNStockInfoFetchRequestURL(symbol:String) -> NSURL? {
  if let components = NSURLComponents(string:kSNStockInfoFetchRequestPath) {
    components.queryItems = [NSURLQueryItem(name:"symbol", value:symbol)]
    return components.URL
  }
  return nil
}

1
Abbastanza pulito, ma queryItemssono disponibili da iOS8.0.
Adil Soomro

Grazie, ha aggiunto un commento per chiarire questo aspetto.
Zorayr

3

Ho seguito il consiglio di Joel sull'uso URLQueryItemse l' ho trasformato in un'estensione Swift (Swift 3)

extension URL
{
    /// Creates an NSURL with url-encoded parameters.
    init?(string : String, parameters : [String : String])
    {
        guard var components = URLComponents(string: string) else { return nil }

        components.queryItems = parameters.map { return URLQueryItem(name: $0, value: $1) }

        guard let url = components.url else { return nil }

        // Kinda redundant, but we need to call init.
        self.init(string: url.absoluteString)
    }
}

(Il self.initmetodo è un po 'scadente, ma non c'era nessun NSURLinit con i componenti)

Può essere usato come

URL(string: "http://www.google.com/", parameters: ["q" : "search me"])

2

Ho un'altra soluzione:

http://splinter.com.au/build-a-url-query-string-in-obj-c-from-a-dict

+(NSString*)urlEscape:(NSString *)unencodedString {
    NSString *s = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
        (CFStringRef)unencodedString,
        NULL,
        (CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ",
        kCFStringEncodingUTF8);
    return [s autorelease]; // Due to the 'create rule' we own the above and must autorelease it
}

// Put a query string onto the end of a url
+(NSString*)addQueryStringToUrl:(NSString *)url params:(NSDictionary *)params {
    NSMutableString *urlWithQuerystring = [[[NSMutableString alloc] initWithString:url] autorelease];
    // Convert the params into a query string
    if (params) {
        for(id key in params) {
            NSString *sKey = [key description];
            NSString *sVal = [[params objectForKey:key] description];
            // Do we need to add ?k=v or &k=v ?
            if ([urlWithQuerystring rangeOfString:@"?"].location==NSNotFound) {
                [urlWithQuerystring appendFormat:@"?%@=%@", [Http urlEscape:sKey], [Http urlEscape:sVal]];
            } else {
                [urlWithQuerystring appendFormat:@"&%@=%@", [Http urlEscape:sKey], [Http urlEscape:sVal]];
            }
        }
    }
    return urlWithQuerystring;
}

Puoi quindi usarlo in questo modo:

NSDictionary *params = @{@"username":@"jim", @"password":@"abc123"};

NSString *urlWithQuerystring = [self addQueryStringToUrl:@"https://myapp.com/login" params:params];

4
Inserisci il codice pertinente nella tua risposta, quel link potrebbe non essere disponibile per sempre.
Madbreaks

2
-(NSString*)encodeDictionary:(NSDictionary*)dictionary{
    NSMutableString *bodyData = [[NSMutableString alloc]init];
    int i = 0;
    for (NSString *key in dictionary.allKeys) {
        i++;
        [bodyData appendFormat:@"%@=",key];
        NSString *value = [dictionary valueForKey:key];
        NSString *newString = [value stringByReplacingOccurrencesOfString:@" " withString:@"+"];
        [bodyData appendString:newString];
        if (i < dictionary.allKeys.count) {
            [bodyData appendString:@"&"];
        }
    }
    return bodyData;
}

Ciò non codifica correttamente i caratteri speciali nei nomi o nei valori (diversi dallo spazio).
jcaron

Non credo che lo spazio debba essere sostituito da + ma piuttosto da% 20 e questo è l'unico carattere cambiato
Przemysław Wrzesiński

1

Ancora un'altra soluzione, se usi RestKit c'è una funzione RKURLEncodedSerializationchiamata RKURLEncodedStringFromDictionaryWithEncodingche fa esattamente quello che vuoi.


1

Un modo semplice per convertire NSDictionary in una stringa di query dell'URL in Objective-c

Es: first_name = Steve & middle_name = Gates & last_name = Jobs & address = Palo Alto, California

    NSDictionary *sampleDictionary = @{@"first_name"         : @"Steve",
                                     @"middle_name"          : @"Gates",
                                     @"last_name"            : @"Jobs",
                                     @"address"              : @"Palo Alto, California"};

    NSMutableString *resultString = [NSMutableString string];
            for (NSString* key in [sampleDictionary allKeys]){
                if ([resultString length]>0)
                    [resultString appendString:@"&"];
                [resultString appendFormat:@"%@=%@", key, [sampleDictionary objectForKey:key]];
            }
NSLog(@"QueryString: %@", resultString);

La speranza aiuterà :)


Ciò non è corretto perché non esegue l'escape di caratteri come "&" se compaiono nei valori del dizionario.
Thomas Tempelmann
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.