从ObjectiveC中的NSDictionary对象创建URL查询参数


90

由于所有的URL处理对象都位于标准可可库中(NSURL,NSMutableURL,NSMutableURLRequest等),我知道我必须忽略一种简单的方法来以编程方式编写GET请求。

目前,我正在手动附加“?” 后跟由“&”连接的名称值对,但是我所有的名称和值对都需要手动编码,因此NSMutableURLRequest在尝试连接到URL时不会完全失败。

感觉好像我应该可以使用预烘焙的API来....是否有任何现成的东西可以将NSDictionary查询参数附加到NSURL?我还有其他方法可以解决这个问题吗?


这么多答案!如此众多的问题和答案投票!仍然没有标记答案。哇!
BangOperator

Answers:


132

在iOS8和OS X 10.10中引入了NSURLQueryItem,可用于构建查询。从关于NSURLQueryItem的文档:

NSURLQueryItem对象表示URL的查询部分中项目的单个名称/值对。您可以将查询项目与NSURLComponents对象的queryItems属性一起使用。

要创建一个,请使用指定的初始化程序queryItemWithName:value:,然后将其添加到中NSURLComponents以生成一个NSURL。例如:

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

请注意,问号和“与”号是自动处理的。NSURL从参数字典创建一个对象非常简单:

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;

我还写了一篇博客文章,介绍如何使用NSURLComponents和构建URL NSURLQueryItems


2
如果字典是嵌套的怎么办?
hariszaman

1
@hariszaman,如果您要通过URL发送嵌套字典,则可能应该考虑通过POST正文发送它。
乔·马西洛蒂

2
请注意,值只能是NSString类型
igrrik

这里只想强调一点:NSURLQueryItem的值只能是字符串。否则可能会导致崩溃!
Pulkit Sharma,

47

您可以NSDictionary为此创建一个类别-在Cocoa库中没有一种我可以找到的标准方法。我使用的代码如下所示:

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

@interface NSDictionary (UrlEncoding)

-(NSString*) urlEncodedString;

@end

通过此实现:

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

我认为该代码非常简单,但是我会在http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html上对其进行更详细的讨论。


16
我认为这行不通。具体来说,您使用的是stringByAddingPercentEscapesUsingEncoding,它不会转义与号以及必须在RFC 3986中指定的查询参数中转义的其他字符。考虑改用MacGoogle Toolbox转义代码
戴夫·派克


30

我想使用克里斯的答案,但是它不是为自动引用计数(ARC)编写的,因此我对其进行了更新。我以为可以粘贴解决方案,以防其他人遇到同样的问题。 注意:self在适当的地方替换为实例或类名。

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

我喜欢这段代码,唯一要添加的就是所有键/值的网址安全编码。
颗粒

22

如果值是字符串,则其他答案效果很好,但是,如果值是字典或数组,则此代码将处理该问题。

重要的是要注意,没有通过查询字符串传递数组/字典的标准方法,但是PHP可以很好地处理此输出

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

例子

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

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

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

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


1
对于最后一个“其他”情况,我添加了对description的调用,以便为NSNumbers提供字符串,而不是对长度的调用推倒:'(CFStringRef)[[params objectForKey:key] description ]'谢谢!
JohnQ 2013年

好答案。但是,您应将CFURLCreateStringByAddingPercentEscapes电话替换为stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR'S

8

我重构并转换为AlBeebe的ARC答案

- (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
这对我来说效果很好,但是我更改了最后一行以包含?。在第一个参数之前:[NSString stringWithFormat:@"?%@",[pairs componentsJoinedByString:@"&"]];
BFar 2014年

1
我认为您应该替换CFURLCreateStringByAddingPercentEscapes通话stringByAddingPercentEncodingWithAllowedCharacters:[NSCharac‌​terSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR's

5

如果您已经在使用AFNetworking(就像我一样),则可以使用它的类AFHTTPRequestSerializer来创建required NSURLRequest

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

如果您只需要工作的URL,请使用NSURLRequest.URL


3

这是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
相当整洁,但queryItems可从iOS8.0获得。
Adil Soomro

谢谢,添加了一条评论以澄清这一点。
Zorayr 2015年

3

我接受了Joel的推荐使用URLQueryItems并变成了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)
    }
}

(该self.init方法有点俗气,但是没有NSURL初始化组件的方法)

可以用作

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

2

我有另一个解决方案:

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

然后可以像这样使用它:

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

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

4
请在您的答案中发布相关代码,该链接可能不会永远存在。
Madbreaks 2012年

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

这不能正确地在名称或值(空格除外)中编码特殊字符。
jcaron 2015年

我不相信空间应该由+而是由20%被替换,这是唯一改变的角色
普热Wrzesiński

1

另一种解决办法,如果你使用RestKit有一个功能RKURLEncodedSerializationRKURLEncodedStringFromDictionaryWithEncoding,你想要做什么。


1

在Objective-c中将NSDictionary转换为url查询字符串的简单方法

例如:first_name = Steve&middle_name = Gates&last_name = Jobs&address =加利福尼亚帕洛阿尔托

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

希望会有所帮助:)


这是不正确的,因为如果出现在字典值中,则不会转义诸如“&”之类的字符。
Thomas Tempelmann '19年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.