在 Objective C 中从 NSDictionary 对象创建 URL 查询参数

Posted

技术标签:

【中文标题】在 Objective C 中从 NSDictionary 对象创建 URL 查询参数【英文标题】:Creating URL query parameters from NSDictionary objects in ObjectiveC 【发布时间】:2010-10-17 15:13:16 【问题描述】:

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

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

这感觉就像我应该能够使用预烘焙的 API 来实现的……有什么开箱即用的东西可以将查询参数的 NSDictionary 附加到 NSURL 吗?我还有其他方法可以解决这个问题吗?

【问题讨论】:

这么多答案!这么多关于问答的投票!仍然没有标记回答。哇! 【参考方案1】:

ios8 和 OS X 10.10 中引入的是NSURLQueryItem,可用于构建查询。来自NSURLQueryItem 上的文档:

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

要创建一个使用指定的初始化器queryItemWithName:value:,然后将它们添加到NSURLComponents 以生成NSURL。例如:

NSURLComponents *components = [NSURLComponents componentsWithString:@"http://***.com"];
NSURLQueryItem *search = [NSURLQueryItem queryItemWithName:@"q" value:@"ios"];
NSURLQueryItem *count = [NSURLQueryItem queryItemWithName:@"count" value:@"10"];
components.queryItems = @[ search, count ];
NSURL *url = components.URL; // http://***.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;

我还写了一篇blog post,介绍如何使用NSURLComponentsNSURLQueryItems 构建URL。

【讨论】:

如果字典是嵌套的怎么办? @hariszaman 如果您通过 URL 发送嵌套字典,您可能应该考虑通过 POST 正文发送它。 请注意,value 只能是 NSString 类型 这里只想强调一点:NSURLQueryItem 的值只能是字符串。如果不是这样,可能会导致崩溃!【参考方案2】:

您可以为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 上更详细地讨论它。

【讨论】:

我认为这行不通。具体来说,您正在使用 stringByAddingPercentEscapesUsingEncoding,它不会转义与号,以及在RFC 3986 中指定的查询参数中必须转义的其他字符。考虑改为查看Google Toolbox For Mac escaping code。 这适用于正确转义:***.com/questions/9187316/…【参考方案3】:

我想使用 Chris 的答案,但它不是为 自动引用计数 (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;

【讨论】:

我喜欢这段代码,唯一需要添加的是编码对于所有键/值都是 url 安全的。【参考方案4】:

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

需要注意的是,没有通过查询字符串传递数组/字典的标准方法,但 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[three]=tres

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

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

【讨论】:

对于最后的 'else' 案例,我添加了对描述的调用,以便它为 NSNumbers 提供字符串,而不是在调用长度时大吵大闹:'(CFStringRef)[[params objectForKey:key] 描述]'谢谢! 很好的答案。但是,您应该将 CFURLCreateStringByAddingPercentEscapes 调用替换为 stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]【参考方案5】:

我重构并转换为 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);

【讨论】:

这对我很有用,但我更改了最后一行以包含 ?第一个参数之前:[NSString stringWithFormat:@"?%@",[pairs componentsJoinedByString:@"&amp;"]]; 我认为你应该替换 CFURLCreateStringByAddingPercentEscapes 调用 stringByAddingPercentEncodingWithAllowedCharacters:[NSCharac‌​terSet URLQueryAllowedCharacterSet]【参考方案6】:

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

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

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

【讨论】:

【参考方案7】:

这是 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

【讨论】:

相当简洁,但 queryItems 可从 iOS8.0 获得。 谢谢,添加评论以澄清这一点。【参考方案8】:

我接受了 Joel 的使用 URLQueryItems 的建议并变成了一个 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)
    

self.init 方法有点俗气,但没有 NSURL init 和组件)

可以作为

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

【讨论】:

【参考方案9】:

我有另一个解决方案:

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

【讨论】:

请在您的答案中发布相关代码,该链接可能不会永远存在。【参考方案10】:
-(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;

【讨论】:

这不会正确编码名称或值中的特殊字符(空格除外)。 我不认为空格应该被替换为 + 而是由 %20 替换,这是唯一改变的字符【参考方案11】:

另一种解决方案,如果您使用 RestKit,RKURLEncodedSerialization 中有一个名为 RKURLEncodedStringFromDictionaryWithEncoding 的函数可以完全满足您的需求。

【讨论】:

【参考方案12】:

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

希望会有所帮助:)

【讨论】:

这是不正确的,因为如果它们出现在字典值中,它不会转义诸如“&”之类的字符。【参考方案13】:

如果您已经在使用AFNetwork,您可以使用他们内置的序列化器来生成编码的 URL;

NSString *baseURL = @"https://api.app.com/parse";
NSDictionary *mutableParameters = [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"true",@"option1", data, @"option2", token, @"token", @"3.0", @"app", nil];
NSURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:baseURL parameters:mutableParameters error:nil];
NSString *urlPath = request.URL.absoluteString;
NSLog(@"%@", urlPath); // https://api.app.com/parse?option1=true&option2=datavalue&token=200%3ATEST%3AENCODE ....

注意;这是对上述答案的扩展。编辑队列已满,因此无法添加到现有答案中。

【讨论】:

以上是关于在 Objective C 中从 NSDictionary 对象创建 URL 查询参数的主要内容,如果未能解决你的问题,请参考以下文章

在 Objective C 中从 web 目录加载多个图像

使用 NSString 变量在 PhoneGap 中从 Objective C 编写 JavaScript

如何比较在Objective C中从NSArray转换的String?

如何在 iOS 8 Objective c 中的 UIWebView 中从文档中加载 png 图像

如何在 iOS Objective C 的嵌套 UICollectionViews 中从 Child UICollectionViewCell 的 UIButton 将值传递给新的 UIViewCon

iOS Objective-C 深入理解Copy