2009-04-05 15 views
83

Con todos los objetos de manejo de URL en las bibliotecas estándar de Cocoa (NSURL, NSMutableURL, NSMutableURLRequest, etc.), sé que debo pasar por alto una forma fácil de componer programáticamente un GET solicitud.Creación de parámetros de consulta URL desde objetos NSDictionary en ObjectiveC

Actualmente estoy agregando manualmente "?" seguidos por pares de valores de nombre 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 utilizar 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?

+0

Tantas respuestas! ¡Tantos votos en preguntas y respuestas! Y todavía no está marcado respondió. Woah! – BangOperator

Respuesta

47

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 utilizo el siguiente aspecto:

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

@interface NSDictionary (UrlEncoding) 

-(NSString*) urlEncodedString; 

@end 

con esta aplicació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 del código bastante sencillo, pero discutir en más detalle en http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html.

+15

No creo que esto funcione. Específicamente, está utilizando stringByAddingPercentEscapesUsingEncoding, que no escapa a ampersands, así como a otros caracteres que deben escaparse en los parámetros de consulta como se especifica en [RFC 3986] (http://www.ietf.org/rfc/rfc3986.txt) . Considere consultar el [código de escape de Google Toolbox For Mac] (http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundation/GTMNSString%2BURLArguments.m). –

+0

Esto funciona para un escape apropiado: http://stackoverflow.com/questions/9187316/string-wont-url-encode-in-ios/9232341#9232341 – JoeCortopassi

2

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

continuación, usted puede utilizarlo como tal:

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

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

Por favor, publique el código relevante en su respuesta, ese enlace puede no estar ahí para siempre . – Madbreaks

28

que quería utilizar la respuesta de Chris, pero no fue escrito para Cuenta de referencia automática (ARC) así que lo actualicé. Pensé en pegar mi solución en caso de que alguien más tenga este mismo problema. Nota: reemplaza 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; 
} 
+0

Me gusta este código, lo único que se debe agregar es que la codificación sea segura para todas las claves/valores. – Pellet

22

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

Es importante señalar que no existe una forma estándar de pasar un array/diccionario a través de la cadena de consulta, pero PHP maneja esta salida 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 & traducciones [uno] = uno & traducciones [dos] = dos & traducciones [tres] = tres

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

foo = bar & traducciones [] = uno & traducciones [] = DOS & traducciones [] = tres

+2

mejor código hasta ahora, gracias. –

+1

Para el caso final 'else', agregue una llamada a la descripción para que proporcione cadenas para NSNumbers en lugar de intervenir en una llamada a longitud: '(CFStringRef) [[params objectForKey: key] ** description **]' ¡Gracias! – JohnQ

+0

gran respuesta. Sin embargo, debe reemplazar las llamadas 'CFURLCreateStringByAddingPercentEscapes' con' stringByAddingPercentEncodingWithAllowedCharacters: [NSCharacterSet URLQueryAllowedCharacterSet] ' –

8

I refactorizado y convertido 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); 
} 
+2

Esto funcionó muy bien para mí, pero cambié la última línea para incluir una? antes de que el primer parámetro: ' [NSString stringWithFormat: @ "?% @", [pares componentsJoinedByString: @" &"]]; – BFar

+0

'Creo que debería sustituir' 'CFURLCreateStringByAddingPercentEscapes' llamadas stringByAddingPercentEncodingWithAllowedCharacters: [NSCharac terSet URLQueryAllowedCharacterSet]' –

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

Esto no lo hace codificar adecuadamente caracteres especiales en nombres o valores (que no sean espacios) – jcaron

+0

No creo que el espacio deba reemplazarse por + sino más bien por% 20 y este es el único carácter que cambió –

126

Introducido en iOS8 y OS X 10.10 es NSURLQueryItem, que se puede utilizar para generar consultas. A partir de los documentos en NSURLQueryItem:

objeto Una NSURLQueryItem representa un solo par nombre/valor para un elemento de la parte de consulta de un URL. Utiliza elementos de consulta con la propiedad queryItems de un objeto NSURLComponents.

Para crear uno utiliza el inicializador designado queryItemWithName:value: y luego añadirlos a NSURLComponents para generar una 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 & se tratan automáticamente. Creación de un NSURL 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 he escrito un blog post sobre cómo construir URL con NSURLComponents y NSURLQueryItems.

+2

¿Qué ocurre si el diccionario está anidado? – hariszaman

+1

@hariszaman if estás enviando un diccionario anidado a través de la URL, probablemente deberías pensar en enviarlo a través del cuerpo POST. –

+0

solo para tener en cuenta, el valor solo puede ser del tipo NSString – igrrik

5

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

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

En caso de que sólo requiere la URL de su trabajo, utilizar NSURLRequest.URL.

3

Aquí está un ejemplo sencillo 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 
} 
+1

Bastante ordenado, pero 'queryItems' están disponibles desde iOS8.0. –

+0

Gracias, agregó un comentario para aclarar esto. – Zorayr

1

Sin embargo, otra solución, si se utiliza RestKit hay una función en RKURLEncodedSerialization llamada RKURLEncodedStringFromDictionaryWithEncoding que hace exactamente lo que quiere.

2

Me tomó la recomendación de Joel de utilizar URLQueryItems y se convirtió en una extensión 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) 
    } 
} 

(El método self.init es un poco cursi, pero no había NSURL init con los componentes)

puede ser utilizado como

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

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

Ex: nombre apellido = Steve & middle_name = Puertas & last_name = Empleo & dirección = 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); 

Esperanza ayudará :)

Cuestiones relacionadas