Creating URL query parameters from NSDictionary ob

2019-01-01 14:58发布

With all the URL-handling objects lying around in the standard Cocoa libraries (NSURL, NSMutableURL, NSMutableURLRequest, etc), I know I must be overlooking an easy way to programmatically compose a GET request.

Currently I'm manually appending "?" followed by name value pairs joined by "&", but all of my name and value pairs need to be manually encoded so NSMutableURLRequest doesn't fail entirely when it tries to connect to the URL.

This feels like something I should be able to use a pre-baked API for.... is there anything out of the box to append an NSDictionary of query parameters to an NSURL? Is there another way I should approach this?

12条回答
长期被迫恋爱
2楼-- · 2019-01-01 15:24

I've got another 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;
}

You can then use it like so:

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

NSString *urlWithQuerystring = [self addQueryStringToUrl:@"https://myapp.com/login" params:params];
查看更多
路过你的时光
3楼-- · 2019-01-01 15:26

You can create a category for NSDictionary to do this -- there isn't a standard way in the Cocoa library that I could find either. The code that I use looks like this:

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

@interface NSDictionary (UrlEncoding)

-(NSString*) urlEncodedString;

@end

with this implementation:

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

I think the code's pretty straightforward, but I discuss it in some more detail at http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html.

查看更多
临风纵饮
4楼-- · 2019-01-01 15:29

I took Joel's recommendation of using URLQueryItems and turned into a 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)
    }
}

(The self.init method is kinda cheesy, but there was no NSURL init with components)

Can be used as

URL(string: "http://www.google.com/", parameters: ["q" : "search me"])
查看更多
临风纵饮
5楼-- · 2019-01-01 15:32

I wanted to use Chris's answer, but it wasn't written for Automatic Reference Counting (ARC) so I updated it. I thought I'd paste my solution in case anyone else has this same issue. Note: replace self with the instance or class name where appropriate.

+(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;
}
查看更多
梦该遗忘
6楼-- · 2019-01-01 15:33
-(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;
}
查看更多
宁负流年不负卿
7楼-- · 2019-01-01 15:34

Here is a simple example 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
}
查看更多
登录 后发表回答