web-dev-qa-db-fra.com

Création de paramètres de requête d'URL à partir d'objets NSDictionary dans Objective C

Avec tous les objets de gestion d'URL qui traînent dans les bibliothèques Cocoa standard (NSURL, NSMutableURL, NSMutableURLRequest, etc.), je sais que je dois ignorer un moyen facile de composer par programmation une demande GET.

Actuellement, j'ajoute manuellement "?" suivi par les paires nom/valeur jointes par "&", mais toutes mes paires nom/valeur doivent être encodées manuellement afin que NSMutableURLRequest n'échoue pas complètement lorsqu'il essaie de se connecter à l'URL.

Cela ressemble à quelque chose pour lequel je devrais pouvoir utiliser une API précuite ... y a-t-il quelque chose de prêt à ajouter un NSDictionary de paramètres de requête à un NSURL? Y a-t-il une autre façon de procéder?

89
user87229

Introduit dans iOS8 et OS X 10.10 est NSURLQueryItem, qui peut être utilisé pour créer des requêtes. À partir des documents sur NSURLQueryItem :

Un objet NSURLQueryItem représente une seule paire nom/valeur pour un élément dans la partie requête d'une URL. Vous utilisez des éléments de requête avec la propriété queryItems d'un objet NSURLComponents.

Pour en créer un, utilisez l'initialiseur désigné queryItemWithName:value: puis ajoutez-les à NSURLComponents pour générer un NSURL. Par exemple:

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

Notez que le point d'interrogation et l'esperluette sont gérés automatiquement. La création d'un NSURL à partir d'un dictionnaire de paramètres est aussi simple que:

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;

J'ai également écrit un article de blog sur la façon de créer des URL avec NSURLComponents et NSURLQueryItems.

132
Joe Masilotti

Vous pouvez créer une catégorie pour NSDictionary pour ce faire - il n'y a pas de moyen standard dans la bibliothèque Cocoa que je puisse trouver non plus. Le code que j'utilise ressemble à ceci:

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

@interface NSDictionary (UrlEncoding)

-(NSString*) urlEncodedString;

@end

avec cette implémentation:

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

Je pense que le code est assez simple, mais j'en discute plus en détail sur http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html .

47
Don McCaughey

Je voulais utiliser la réponse de Chris, mais elle n'a pas été écrite pour Comptage de référence automatique (ARC) alors je l'ai mis à jour. J'ai pensé coller ma solution au cas où quelqu'un d'autre aurait ce même problème. Remarque: remplacez self par le nom de l'instance ou de la classe, le cas échéant.

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

Les autres réponses fonctionnent très bien si les valeurs sont des chaînes, mais si les valeurs sont des dictionnaires ou des tableaux, alors ce code gérera cela.

Il est important de noter qu'il n'y a pas de moyen standard de passer un tableau/dictionnaire via la chaîne de requête mais PHP gère cette sortie très bien

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

Exemples

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

foo = bar & traductions [one] = uno & translations [two] = dos & translations [three] = tres

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

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

22
AlBeebe

J'ai refactorisé et converti en réponse ARC par 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);
}
8
Renetik

Si vous utilisez déjà AFNetworking (comme ce fut le cas avec moi), vous pouvez utiliser sa classe AFHTTPRequestSerializer pour créer le NSURLRequest requis.

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

Si vous n'avez besoin que de l'URL pour votre travail, utilisez NSURLRequest.URL.

5
Ayush Goel

Voici un exemple simple dans 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
}
3
Zorayr

J'ai suivi la recommandation de Joel d'utiliser URLQueryItems et je me suis transformé en une extension 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)
    }
}

(Le self.init la méthode est un peu ringarde, mais il n'y avait pas de NSURL init avec des composants)

Peut être utilisé comme

URL(string: "http://www.google.com/", parameters: ["q" : "search me"])
3
Can
-(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;
}

J'ai une autre solution:

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

Vous pouvez ensuite l'utiliser comme ceci:

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

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

Un moyen simple de convertir NSDictionary en chaîne de requête URL dans Objective-c

Ex: first_name = Steve & middle_name = Gates & last_name = Jobs & address = Palo Alto, Californie

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

L'espoir vous aidera :)

1
vidalbenjoe

Encore une autre solution, si vous utilisez RestKit il y a une fonction dans RKURLEncodedSerialization appelée RKURLEncodedStringFromDictionaryWithEncoding qui fait exactement ce que vous voulez.

1
cicerocamargo