Erstellen von URL-Abfrageparametern aus NSDictionary-Objekten in ObjectiveC

90

Bei all den URL-verarbeitenden Objekten, die in den Standard-Cocoa-Bibliotheken (NSURL, NSMutableURL, NSMutableURLRequest usw.) herumliegen, muss ich eine einfache Möglichkeit zum programmgesteuerten Erstellen einer GET-Anforderung übersehen.

Derzeit füge ich manuell "?" gefolgt von Namenswertpaaren, die durch "&" verbunden sind, aber alle meine Namens- und Wertepaare müssen manuell codiert werden, damit NSMutableURLRequest nicht vollständig fehlschlägt, wenn versucht wird, eine Verbindung zur URL herzustellen.

Dies scheint etwas zu sein, für das ich eine vorgebackene API verwenden sollte. Gibt es etwas, das nicht in der Lage ist, ein NSDictionary mit Abfrageparametern an eine NSURL anzuhängen? Gibt es einen anderen Weg, wie ich das angehen sollte?

Zev Eisenberg
quelle
So viele Antworten! So viele Stimmen zu Fragen und Antworten! Und immer noch ist es nicht markiert beantwortet. Woah!
BangOperator

Antworten:

132

Eingeführt in iOS8 und OS X 10.10 ist NSURLQueryItem, dass zum Erstellen von Abfragen verwendet werden kann. Aus den Dokumenten zu NSURLQueryItem :

Ein NSURLQueryItem-Objekt repräsentiert ein einzelnes Name / Wert-Paar für ein Element im Abfrageteil einer URL. Sie verwenden Abfrageelemente mit der Eigenschaft queryItems eines NSURLComponents-Objekts.

Um einen zu erstellen, verwenden Sie den angegebenen Initialisierer queryItemWithName:value:und fügen Sie ihn hinzu, NSURLComponentsum einen zu generieren NSURL. Beispielsweise:

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

Beachten Sie, dass das Fragezeichen und das kaufmännische Und automatisch behandelt werden. Das Erstellen eines NSURLaus einem Wörterbuch von Parametern ist so einfach wie:

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;

Ich habe auch einen Blog-Beitrag darüber geschrieben, wie man URLs mit NSURLComponentsund erstellt NSURLQueryItems.

Joe Masilotti
quelle
2
Was ist, wenn das Wörterbuch verschachtelt ist?
Hariszaman
1
@hariszaman Wenn Sie ein verschachteltes Wörterbuch über die URL senden, sollten Sie wahrscheinlich darüber nachdenken, es stattdessen über den POST-Text zu senden.
Joe Masilotti
2
Nur zu beachten, Wert kann nur vom Typ NSString sein
igrrik
Ich möchte hier nur einen Punkt hervorheben: Der Wert für ein NSURLQueryItem kann nur eine Zeichenfolge sein. Es kann zu einem Absturz kommen, wenn dies nicht der Fall ist!
Pulkit Sharma
47

Sie können eine Kategorie dafür erstellen NSDictionary- es gibt keinen Standardweg in der Kakaobibliothek, den ich auch finden könnte. Der Code, den ich verwende, sieht folgendermaßen aus:

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

@interface NSDictionary (UrlEncoding)

-(NSString*) urlEncodedString;

@end

mit dieser Implementierung:

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

Ich denke, der Code ist ziemlich einfach, aber ich diskutiere ihn ausführlicher unter http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html .

Don McCaughey
quelle
16
Ich denke nicht, dass das funktioniert. Insbesondere verwenden Sie stringByAddingPercentEscapesUsingEncoding, das kein kaufmännisches Und-Zeichen enthält, sowie andere Zeichen, die in Abfrageparametern maskiert werden müssen, wie in RFC 3986 angegeben . Schauen Sie sich stattdessen den Escape-Code der Google Toolbox für Mac an .
Dave Peck
Dies funktioniert für die ordnungsgemäße Flucht: stackoverflow.com/questions/9187316/…
JoeCortopassi
30

Ich wollte Chris 'Antwort verwenden, aber sie wurde nicht für die automatische Referenzzählung (ARC) geschrieben, daher habe ich sie aktualisiert. Ich dachte, ich würde meine Lösung einfügen, falls jemand anderes das gleiche Problem hat. Hinweis: Ersetzen Sie selfgegebenenfalls durch den Instanz- oder Klassennamen.

+(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;
}
Anonymer Entwickler
quelle
Ich mag diesen Code. Das einzige, was ich hinzufügen muss, ist, dass die Codierung für alle Schlüssel / Werte url-sicher ist.
Pellet
22

Die anderen Antworten funktionieren hervorragend, wenn die Werte Zeichenfolgen sind. Wenn die Werte jedoch Wörterbücher oder Arrays sind, wird dieser Code dies verarbeiten.

Es ist wichtig zu beachten, dass es keine Standardmethode gibt, um ein Array / Wörterbuch über die Abfragezeichenfolge zu übergeben, aber PHP verarbeitet diese Ausgabe einwandfrei

-(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:@"&"];
}

Beispiele

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

foo = bar & translations [eins] = uno & translations [zwei] = dos & translations [drei] = tres

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

foo = bar & translations [] = uno & translations [] = dos & translations [] = tres

AlBeebe
quelle
1
Für den letzten 'else'-Fall habe ich der Beschreibung einen Aufruf hinzugefügt, damit Zeichenfolgen für NSNumbers bereitgestellt werden, anstatt einen Aufruf zur Länge zu sperren:' (CFStringRef) [[params objectForKey: key] description ] 'Danke!
JohnQ
gute Antwort. Sie sollten jedoch CFURLCreateStringByAddingPercentEscapesAnrufe durchstringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR
8

Ich habe AlBeebe überarbeitet und auf ARC-Antwort umgestellt

- (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);
}
Renetik
quelle
2
Das hat bei mir gut funktioniert, aber ich habe die letzte Zeile geändert, um ein? vor dem ersten Parameter:[NSString stringWithFormat:@"?%@",[pairs componentsJoinedByString:@"&"]];
BFar
1
Ich denke, Sie sollten CFURLCreateStringByAddingPercentEscapesAnrufe ersetzenstringByAddingPercentEncodingWithAllowedCharacters:[NSCharac‌​terSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR
5

Wenn Sie AFNetworking bereits verwenden (wie es bei mir der Fall war), können Sie die Klasse verwenden AFHTTPRequestSerializer, um das erforderliche zu erstellen NSURLRequest.

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

Wenn Sie nur die URL für Ihre Arbeit benötigen, verwenden Sie NSURLRequest.URL.

Ayush Goel
quelle
3

Hier ist ein einfaches Beispiel 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
}
Zorayr
quelle
1
Ziemlich ordentlich, aber queryItemsab iOS8.0 verfügbar.
Adil Soomro
Vielen Dank, fügte einen Kommentar hinzu, um dies zu verdeutlichen.
Zorayr
3

Ich nahm Joels Empfehlung an URLQueryItemsund verwandelte mich in eine Swift-Erweiterung (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)
    }
}

(Die self.initMethode ist etwas kitschig, aber es gab keine NSURLInit mit Komponenten)

Kann als verwendet werden

URL(string: "http://www.google.com/", parameters: ["q" : "search me"])
Können
quelle
2

Ich habe eine andere Lösung:

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

Sie können es dann so verwenden:

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

NSString *urlWithQuerystring = [self addQueryStringToUrl:@"https://myapp.com/login" params:params];
Chris
quelle
4
Bitte geben Sie den entsprechenden Code in Ihre Antwort ein. Dieser Link ist möglicherweise nicht für immer verfügbar.
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;
}
Filippo Ozino Caligaris
quelle
Dadurch werden Sonderzeichen in Namen oder Werten (außer Leerzeichen) nicht ordnungsgemäß codiert.
JCaron
Ich glaube nicht, dass Leerzeichen durch + ersetzt werden sollten, sondern durch% 20, und dies ist das einzige Zeichen, das geändert wurde
Przemysław Wrzesiński
1

Eine weitere Lösung: Wenn Sie RestKit verwenden , wird eine Funktion RKURLEncodedSerializationaufgerufen RKURLEncodedStringFromDictionaryWithEncoding, die genau das tut, was Sie wollen.

Cicerocamargo
quelle
1

Einfache Möglichkeit zum Konvertieren von NSDictionary in eine URL-Abfragezeichenfolge in Objective-c

Beispiel: Vorname = Steve & zweiter Vorname = Gates & Nachname = Jobs & Adresse = Palo Alto, Kalifornien

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

Hoffnung wird helfen :)

vidalbenjoe
quelle
Dies ist falsch, da Zeichen wie "&" nicht ausgeblendet werden, wenn sie in den Wörterbuchwerten angezeigt werden.
Thomas Tempelmann