Tạo tham số truy vấn URL từ các đối tượng NSDictionary trong ObjectiveC


90

Với tất cả các đối tượng xử lý URL nằm xung quanh trong các thư viện Cocoa tiêu chuẩn (NSURL, NSMutableURL, NSMutableURLRequest, v.v.), tôi biết mình phải bỏ qua một cách dễ dàng để soạn một yêu cầu GET theo chương trình.

Hiện tại tôi đang thêm "?" theo sau là các cặp giá trị tên được nối bởi "&", nhưng tất cả các cặp tên và giá trị của tôi cần được mã hóa theo cách thủ công để NSMutableURLRequest không bị lỗi hoàn toàn khi cố gắng kết nối với URL.

Điều này giống như một cái gì đó tôi sẽ có thể sử dụng một API được tạo sẵn cho .... có bất cứ điều gì khác để thêm một NSDictionary các tham số truy vấn vào một NSURL không? Có cách nào khác tôi nên tiếp cận điều này không?


Rất nhiều câu trả lời! Rất nhiều phiếu bầu cho câu hỏi và câu trả lời! Và nó vẫn chưa được đánh dấu trả lời. Chà!
BangOperator

Câu trả lời:


132

Được giới thiệu trong iOS8 và OS X 10.10 NSURLQueryItem, có thể được sử dụng để xây dựng các truy vấn. Từ tài liệu trên NSURLQueryItem :

Một đối tượng NSURLQueryItem đại diện cho một cặp tên / giá trị duy nhất cho một mục trong phần truy vấn của URL. Bạn sử dụng các mục truy vấn với thuộc tính queryItems của một đối tượng NSURLComponents.

Để tạo một queryItemWithName:value:tệp, hãy sử dụng trình khởi tạo được chỉ định và sau đó thêm chúng vào NSURLComponentsđể tạo NSURL. Ví dụ:

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

Lưu ý rằng dấu hỏi và dấu và được tự động xử lý. Tạo một NSURLtừ điển các tham số đơn giản như sau:

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;

Tôi cũng đã viết một bài đăng trên blog về cách tạo URL với NSURLComponentsNSURLQueryItems.


2
nếu từ điển được lồng vào nhau thì sao?
hariszaman

1
@hariszaman nếu bạn đang gửi một từ điển lồng nhau qua URL, bạn có thể nên nghĩ đến việc gửi nó qua phần nội dung POST.
Joe Masilotti

2
chỉ cần lưu ý, giá trị chỉ có thể thuộc loại NSString
igrrik

Chỉ muốn nhấn mạnh một điểm ở đây: giá trị cho một NSURLQueryItem chỉ có thể là một chuỗi. Nó có thể dẫn đến sự cố nếu không phải trường hợp!
Pulkit Sharma

47

Bạn có thể tạo một danh mục NSDictionaryđể thực hiện việc này - không có cách tiêu chuẩn nào trong thư viện Cocoa mà tôi có thể tìm thấy. Mã mà tôi sử dụng trông giống như sau:

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

@interface NSDictionary (UrlEncoding)

-(NSString*) urlEncodedString;

@end

với cách triển khai này:

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

Tôi nghĩ rằng mã này khá đơn giản, nhưng tôi sẽ thảo luận chi tiết hơn về nó tại http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html .


16
Tôi không nghĩ rằng điều này hiệu quả. Cụ thể, bạn đang sử dụng stringByAddingPercentEscapesUsingEncoding, không thoát khỏi ký hiệu và cũng như các ký tự khác phải được thoát trong các tham số truy vấn như được chỉ định trong RFC 3986 . Thay vào đó, hãy xem xét mã thoát của Hộp công cụ Google dành cho Mac .
Dave Peck

Này làm việc cho thoát thích hợp: stackoverflow.com/questions/9187316/...
JoeCortopassi

30

Tôi muốn sử dụng câu trả lời của Chris, nhưng nó không được viết cho Đếm Tham chiếu Tự động (ARC) nên tôi đã cập nhật nó. Tôi nghĩ rằng tôi sẽ dán giải pháp của mình trong trường hợp bất kỳ ai khác gặp vấn đề tương tự. Lưu ý: thay thế selfbằng tên cá thể hoặc lớp nếu thích hợp.

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

Tôi thích mã này, điều duy nhất cần thêm là mã hóa url an toàn cho tất cả các khóa / giá trị.
Pellet

22

Các câu trả lời khác hoạt động tốt nếu giá trị là chuỗi, tuy nhiên nếu giá trị là từ điển hoặc mảng thì mã này sẽ xử lý điều đó.

Điều quan trọng cần lưu ý là không có cách tiêu chuẩn nào để truyền một mảng / từ điển qua chuỗi truy vấn nhưng PHP xử lý đầu ra này rất tốt

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

Ví dụ

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

foo = bar & translate [one] = una & dịch [hai] = dos & dịch [ba] = tres

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

foo = bar & translate [] = una & translate [] = dos & translate [] = tres


1
Đối với trường hợp 'khác' cuối cùng, tôi đã thêm một lệnh gọi đến mô tả để nó cung cấp các chuỗi cho NSNumbers thay vì cản trở lệnh gọi độ dài: '(CFStringRef) [[params objectForKey: key] description ]' Cảm ơn!
JohnQ

câu trả lời chính xác. Tuy nhiên, bạn nên thay thế CFURLCreateStringByAddingPercentEscapescác cuộc gọi bằngstringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR

8

Tôi đã cấu trúc lại và chuyển đổi thành câu trả lời ARC bởi 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
Điều này rất hiệu quả đối với tôi, nhưng tôi đã thay đổi dòng cuối cùng để bao gồm một? trước thông số đầu tiên:[NSString stringWithFormat:@"?%@",[pairs componentsJoinedByString:@"&"]];
BFar

1
tôi nghĩ bạn nên thay thế CFURLCreateStringByAddingPercentEscapescác cuộc gọistringByAddingPercentEncodingWithAllowedCharacters:[NSCharac‌​terSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR

5

Nếu bạn đang sử dụng AFNetworking (như trường hợp của tôi), bạn có thể sử dụng lớp của nó AFHTTPRequestSerializerđể tạo các yêu cầu NSURLRequest.

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

Trong trường hợp bạn chỉ yêu cầu URL cho công việc của mình, hãy sử dụng NSURLRequest.URL.


3

Đây là một ví dụ đơn giản trong 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
Khá gọn gàng, nhưng queryItemscó sẵn từ iOS8.0.
Adil Soomro

Cảm ơn, đã thêm một bình luận để làm rõ điều này.
Zorayr

3

Tôi đã nhận đề xuất của Joel về việc sử dụng URLQueryItemsvà chuyển thành 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.initPhương pháp này hơi phô trương, nhưng không có liên NSURLkết với các thành phần)

Có thể được sử dụng như

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

2

Tôi có một giải pháp khác:

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

Sau đó, bạn có thể sử dụng nó như vậy:

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

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

4
Vui lòng đăng mã có liên quan trong câu trả lời của bạn, liên kết đó có thể không tồn tại mãi mãi.
Madbreaks 13/12/12

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

Điều này không mã hóa đúng các ký tự đặc biệt trong tên hoặc giá trị (ngoài dấu cách).
jcaron

Tôi không tin rằng không gian nên được thay thế bằng + nhưng thay bằng% 20 và đây là nhân vật duy nhất thay đổi
Przemysław Wrzesinski

1

Tuy nhiên, một giải pháp khác, nếu bạn sử dụng RestKit, có một chức năng RKURLEncodedSerializationđược gọi là RKURLEncodedStringFromDictionaryWithEncodingthực hiện chính xác những gì bạn muốn.


1

Cách đơn giản để chuyển đổi NSDictionary thành chuỗi truy vấn url trong Objective-c

Ví dụ: first_name = Steve & middle_name = Gates & last_name = Công việc & địa chỉ = 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);

Hy vọng sẽ giúp ích :)


Điều này không chính xác vì nó không thoát khỏi các ký tự như "&" nếu chúng xuất hiện trong các giá trị từ điển.
Thomas Tempelmann
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.