Crear parámetros de consulta de URL desde objetos NSDictionary en ObjectiveC

Con todos los objetos de manejo de URL que se encuentran en las bibliotecas estándar de Cocoa (NSURL, NSMutableURL, NSMutableURLRequest, etc.), sé que debo pasar por alto una forma fácil de redactar programáticamente una solicitud GET.

Actualmente estoy agregando manualmente “?” seguidos de pares de valores nominales unidos por “&”, pero todos mis pares de nombre y valor deben codificarse manualmente para que NSMutableURLRequest no falle por completo cuando intenta conectarse a la URL.

Esto se siente como algo que debería ser capaz de usar una API precocida para … ¿hay algo listo para usar para agregar un NSDictionary de parámetros de consulta a un NSURL? ¿Hay alguna otra manera de acercarme a esto?

Introducido en iOS8 y OS X 10.10 es NSURLQueryItem , que se puede usar para crear consultas. De los documentos en NSURLQueryItem :

Un objeto NSURLQueryItem representa un único par nombre / valor para un elemento en la parte de consulta de una URL. Utiliza elementos de consulta con la propiedad queryItems de un objeto NSURLComponents.

Para crear uno use el inicializador designado queryItemWithName:value: y luego agréguelos a NSURLComponents para generar un NSURL . Por ejemplo:

 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 

Observe que el signo de interrogación y el signo de & se manejan automáticamente. Crear un NSURL partir de un diccionario de parámetros es tan simple como:

 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; 

También escribí una publicación de blog sobre cómo comstackr URL con NSURLComponents y NSURLQueryItems .

Puede crear una categoría para NSDictionary para hacer esto: no hay una forma estándar en la biblioteca Cocoa que pueda encontrar. El código que uso se ve así:

 // file "NSDictionary+UrlEncoding.h" #import  @interface NSDictionary (UrlEncoding) -(NSString*) urlEncodedString; @end 

con esta implementación:

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

Creo que el código es bastante sencillo, pero lo analizo con más detalle en http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html .

Quería usar la respuesta de Chris, pero no fue escrita para el recuento automático de referencias (ARC), así que la actualicé. Pensé en pegar mi solución en caso de que alguien más tenga este mismo problema. Nota: reemplace self con la instancia o el nombre de la clase cuando corresponda.

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

Las otras respuestas funcionan muy bien si los valores son cadenas, sin embargo, si los valores son diccionarios o matrices, entonces este código manejará eso.

Es importante tener en cuenta que no hay una forma estándar de pasar una matriz / diccionario a través de la cadena de consulta, pero PHP maneja esta salida muy 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:@"&"]; } 

Ejemplos

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

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

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

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

Refactoré y convertí a ARC respuesta por 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); } 

Si ya está utilizando AFNetworking (como fue el caso conmigo), puede usar su clase AFHTTPRequestSerializer para crear la NSURLRequest requerida.

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

En caso de que solo requiera la URL para su trabajo, use NSURLRequest.URL .

Aquí hay un ejemplo simple en 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 } 

Tengo otra solución:

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

Puede usarlo así:

 NSDictionary *params = @{@"username":@"jim", @"password":@"abc123"}; NSString *urlWithQuerystring = [self addQueryStringToUrl:@"https://myapp.com/login" params:params]; 
 -(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; } 

Tomé la recomendación de Joel de usar URLQueryItems y se convirtió en Swift Extension (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) } } 

(El método self.init es un poco cursi, pero no hubo NSURL init con componentes)

Se puede usar como

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

Manera simple de convertir NSDictionary a cadena de consulta url en Objective-c

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

Hope ayudará 🙂

Otra solución más, si utiliza RestKit, hay una función en RKURLEncodedSerialization llamada RKURLEncodedStringFromDictionaryWithEncoding que hace exactamente lo que usted desea.