Membuat parameter kueri URL dari objek NSDictionary di ObjectiveC

90

Dengan semua objek penanganan URL yang tergeletak di pustaka Kakao standar (NSURL, NSMutableURL, NSMutableURLRequest, dll), saya tahu saya harus mengabaikan cara mudah untuk membuat permintaan GET secara terprogram.

Saat ini saya menambahkan "?" Secara manual diikuti oleh pasangan nilai nama yang digabungkan dengan "&", tetapi semua pasangan nama dan nilai saya harus dienkode secara manual sehingga NSMutableURLRequest tidak gagal sepenuhnya saat mencoba menyambung ke URL.

Ini terasa seperti sesuatu yang saya harus dapat menggunakan API pra-panggang untuk .... apakah ada sesuatu di luar kotak untuk menambahkan NSDictionary parameter kueri ke NSURL? Apakah ada cara lain saya harus mendekati ini?

Zev Eisenberg
sumber
Begitu banyak jawaban! Begitu banyak suara untuk pertanyaan dan jawaban! Dan masih belum ditandai terjawab. Wow!
BangOperator

Jawaban:

132

Diperkenalkan di iOS8 dan OS X 10.10 NSURLQueryItem, yang dapat digunakan untuk membuat kueri. Dari dokumen di NSURLQueryItem :

Objek NSURLQueryItem mewakili satu pasangan nama / nilai untuk sebuah item di bagian kueri URL. Anda menggunakan item kueri dengan properti queryItems dari objek NSURLComponents.

Untuk membuatnya, gunakan penginisialisasi yang ditunjuk queryItemWithName:value:dan kemudian tambahkan ke NSURLComponentsuntuk menghasilkan NSURL. Sebagai contoh:

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

Perhatikan bahwa tanda tanya dan ampersand ditangani secara otomatis. Membuat NSURLdari kamus parameter sesederhana:

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;

Saya juga telah menulis posting blog tentang cara membuat URL dengan NSURLComponentsdan NSURLQueryItems.

Joe Masilotti
sumber
2
bagaimana jika kamus itu bersarang?
hariszaman
1
@hariszaman jika Anda mengirim kamus bertingkat melalui URL, Anda mungkin harus berpikir untuk mengirimnya melalui badan POST.
Joe Masilotti
2
Sekadar
Hanya ingin menekankan satu hal di sini: nilai untuk NSURLQueryItem hanya dapat berupa string. Ini dapat mengakibatkan crash jika tidak demikian!
Pulkit Sharma
47

Anda dapat membuat kategori untuk NSDictionarymelakukan ini - tidak ada cara standar di pustaka Cocoa yang dapat saya temukan. Kode yang saya gunakan terlihat seperti ini:

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

@interface NSDictionary (UrlEncoding)

-(NSString*) urlEncodedString;

@end

dengan implementasi ini:

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

Saya pikir kodenya cukup mudah, tetapi saya membahasnya lebih detail di http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html .

Don McCaughey
sumber
16
Saya tidak berpikir ini berhasil. Secara khusus, Anda menggunakan stringByAddingPercentEscapesUsingEncoding, yang tidak lolos dari ampersand, serta karakter lain yang harus di-escape dalam parameter kueri seperti yang ditentukan di RFC 3986 . Pertimbangkan untuk melihat kode pelolosan Google Toolbox Untuk Mac .
Dave Peck
Ini berfungsi untuk pelolosan yang tepat: stackoverflow.com/questions/9187316/…
JoeCortopassi
30

Saya ingin menggunakan jawaban Chris, tetapi itu tidak ditulis untuk Penghitungan Referensi Otomatis (ARC) jadi saya memperbaruinya. Saya pikir saya akan menempelkan solusi saya jika ada orang lain yang memiliki masalah yang sama ini. Catatan: ganti selfdengan instance atau nama kelas jika perlu.

+(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;
}
Pengembang Anonim
sumber
Saya suka kode ini, satu-satunya hal yang harus ditambahkan adalah agar pengkodean menjadi url aman untuk semua kunci / nilai.
Pellet
22

Jawaban lain bekerja dengan baik jika nilainya adalah string, namun jika nilainya adalah kamus atau array maka kode ini akan menanganinya.

Penting untuk dicatat bahwa tidak ada cara standar untuk melewatkan array / kamus melalui string kueri tetapi PHP menangani keluaran ini dengan baik

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

Contoh

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

foo = bar & terjemahan [one] = uno & terjemahan [dua] = dos & terjemahan [tiga] = tres

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

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

AlBeebe
sumber
1
Untuk kasus 'lain' terakhir, saya menambahkan panggilan ke deskripsi sehingga akan memberikan string untuk NSNumbers daripada muntah pada panggilan ke panjang: '(CFStringRef) [[params objectForKey: key] description ]' Terima kasih!
JohnQ
jawaban yang bagus. Namun Anda harus mengganti CFURLCreateStringByAddingPercentEscapespanggilan denganstringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR
8

Saya refactored dan dikonversi ke jawaban ARC oleh 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);
}
Renetik
sumber
2
Ini bekerja dengan baik untuk saya, tetapi saya mengubah baris terakhir untuk menyertakan? sebelum param pertama:[NSString stringWithFormat:@"?%@",[pairs componentsJoinedByString:@"&"]];
BFar
1
saya pikir Anda harus mengganti CFURLCreateStringByAddingPercentEscapespanggilanstringByAddingPercentEncodingWithAllowedCharacters:[NSCharac‌​terSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR
5

Jika Anda sudah menggunakan AFNetworking (seperti yang terjadi dengan saya), Anda dapat menggunakan kelasnya AFHTTPRequestSerializeruntuk membuat yang diperlukan NSURLRequest.

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

Jika Anda hanya memerlukan URL untuk pekerjaan Anda, gunakan NSURLRequest.URL.

Ayush Goel
sumber
3

Berikut adalah contoh sederhana di 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
}
Zorayr
sumber
1
Cukup rapi, tetapi queryItemstersedia dari iOS8.0.
Adil Soomro
Terima kasih, menambahkan komentar untuk mengklarifikasi ini.
Zorayr
3

Saya mengambil rekomendasi Joel untuk menggunakan URLQueryItemsdan berubah menjadi 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.initMetode ini agak murahan, tetapi tidak ada NSURLinit dengan komponen)

Bisa digunakan sebagai

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

Saya punya solusi lain:

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

Anda kemudian dapat menggunakannya seperti ini:

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

NSString *urlWithQuerystring = [self addQueryStringToUrl:@"https://myapp.com/login" params:params];
Chris
sumber
4
Harap posting kode yang relevan dalam jawaban Anda, link tersebut mungkin tidak akan ada selamanya.
Madbreaks
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;
}
Filippo Ozino Caligaris
sumber
Ini tidak mengenkode karakter khusus dengan benar dalam nama atau nilai (selain spasi).
jcaron
Saya tidak percaya spasi harus diganti dengan + melainkan oleh% 20 dan ini adalah satu-satunya karakter yang diubah
Przemysław Wrzesiński
1

Namun solusi lain, jika Anda menggunakan RestKit, ada fungsi yang RKURLEncodedSerializationdipanggil RKURLEncodedStringFromDictionaryWithEncodingyang melakukan apa yang Anda inginkan.

cicerocamargo
sumber
1

Cara sederhana untuk mengubah NSDictionary menjadi string kueri url di Objective-c

Contoh: first_name = Steve & middle_name = Gates & last_name = Pekerjaan & alamat = 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);

Semoga bisa membantu :)

vidalbenjoe
sumber
Ini tidak benar karena tidak lolos dari karakter seperti "&" jika muncul di nilai kamus.
Thomas Tempelmann