การสร้างพารามิเตอร์การสืบค้น URL จากวัตถุ NSDictionary ใน ObjectiveC


90

ด้วยอ็อบเจ็กต์การจัดการ URL ทั้งหมดที่อยู่รอบ ๆ ไลบรารี Cocoa มาตรฐาน (NSURL, NSMutableURL, NSMutableURLRequest ฯลฯ ) ฉันรู้ว่าฉันต้องมองข้ามวิธีง่ายๆในการเขียนคำขอ GET โดยใช้โปรแกรม

ขณะนี้ฉันกำลังต่อท้าย "?" ด้วยตนเอง ตามด้วยคู่ค่าชื่อที่เข้าร่วมด้วย "&" แต่คู่ชื่อและค่าทั้งหมดของฉันต้องเข้ารหัสด้วยตนเองดังนั้น NSMutableURLRequest จะไม่ล้มเหลวทั้งหมดเมื่อพยายามเชื่อมต่อกับ URL

รู้สึกเหมือนมีบางอย่างที่ฉันควรจะสามารถใช้ API ที่อบไว้ล่วงหน้าสำหรับ .... มีอะไรนอกกรอบที่จะผนวก NSDictionary ของพารามิเตอร์การสืบค้นเข้ากับ NSURL หรือไม่? มีวิธีอื่นอีกไหมที่ฉันควรเข้าใกล้สิ่งนี้


คำตอบมากมาย! โหวตคำถามและคำตอบมากมาย! และยังไม่มีเครื่องหมายตอบ ว้าว!
BangOperator

คำตอบ:


132

แนะนำใน iOS8 และ OS X 10.10 คือNSURLQueryItemซึ่งสามารถใช้ในการสร้างแบบสอบถาม จากเอกสารบนNSURLQueryItem :

อ็อบเจ็กต์ NSURLQueryItem แสดงคู่ชื่อ / ค่าเดียวสำหรับรายการในส่วนเคียวรีของ URL คุณใช้ไอเท็มเคียวรีกับคุณสมบัติ queryItems ของอ็อบเจ็กต์ NSURLComponents

ในการสร้างหนึ่งให้ใช้ initializer ที่กำหนด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;

ผมเคยเขียนยังมีการโพสต์บล็อกเกี่ยวกับวิธีการสร้าง URL ที่มีและNSURLComponentsNSURLQueryItems


2
จะเกิดอะไรขึ้นถ้าพจนานุกรมซ้อนกัน?
hariszaman

1
@hariszaman หากคุณส่งพจนานุกรมที่ซ้อนกันผ่าน URL คุณน่าจะคิดว่าจะส่งผ่านเนื้อหา POST แทน
Joe Masilotti

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 ลองพิจารณาดูโค้ด Escape ของ Google Toolbox For Macแทน
Dave Peck

สิ่งนี้ใช้ได้ผลสำหรับการหลบหนีที่เหมาะสม: stackoverflow.com/questions/9187316/…
JoeCortopassi

30

ฉันต้องการใช้คำตอบของ Chris แต่ไม่ได้เขียนไว้สำหรับAutomatic Reference Counting (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 ที่ปลอดภัยสำหรับคีย์ / ค่าทั้งหมด
Pellet

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 [three] = tres

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

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


1
สำหรับกรณี 'else' ขั้นสุดท้ายฉันได้เพิ่มการเรียกคำอธิบายเพื่อให้สตริงสำหรับ NSNumbers แทนที่จะเป็นการ จำกัด การเรียกให้ยาว: '(CFStringRef) [[params objectForKey: key] description ]' ขอบคุณ!
JohnQ

คำตอบที่ดี อย่างไรก็ตามคุณควรแทนที่การCFURLCreateStringByAddingPercentEscapesโทรด้วยstringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR

8

ฉันปรับโครงสร้างใหม่และแปลงเป็นคำตอบ ARC โดย 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
วิธีนี้ใช้ได้ผลดีสำหรับฉัน แต่ฉันเปลี่ยนบรรทัดสุดท้ายเพื่อรวม? ก่อนพารามิเตอร์ตัวแรก:[NSString stringWithFormat:@"?%@",[pairs componentsJoinedByString:@"&"]];
BFar

1
ฉันคิดว่าคุณควรเปลี่ยนการCFURLCreateStringByAddingPercentEscapesโทรstringByAddingPercentEncodingWithAllowedCharacters:[NSCharac‌​terSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR

5

หากคุณใช้AFNetworkingอยู่แล้ว(เช่นเดียวกับฉัน) คุณสามารถใช้คลาสนี้AFHTTPRequestSerializerเพื่อสร้างไฟล์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

3

ฉันรับคำแนะนำของ 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เริ่มต้นด้วยส่วนประกอบ)

สามารถใช้เป็น

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

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

ฉันไม่เชื่อว่าควรแทนที่ช่องว่างด้วย + แต่เป็น% 20 และนี่เป็นตัวละครเดียวที่เปลี่ยนไป
PrzemysławWrzesiński

1

อีกวิธีหนึ่งหากคุณใช้RestKit จะมีฟังก์ชันที่RKURLEncodedSerializationเรียกRKURLEncodedStringFromDictionaryWithEncodingว่าทำในสิ่งที่คุณต้องการ


1

วิธีง่ายๆในการแปลง NSDictionary เป็นสตริงการสืบค้น url ใน Objective-c

เช่น first_name = Steve & middle_name = Gates & last_name = Jobs & address = 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);

หวังว่าจะช่วยได้ :)


สิ่งนี้ไม่ถูกต้องเนื่องจากไม่ได้หลีกเลี่ยงอักขระเช่น "&" หากปรากฏในค่าพจนานุกรม
Thomas Tempelmann
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.