ObjectiveC의 NSDictionary 개체에서 URL 쿼리 매개 변수 만들기


90

표준 Cocoa 라이브러리 (NSURL, NSMutableURL, NSMutableURLRequest 등)에있는 모든 URL 처리 개체를 사용하여 프로그래밍 방식으로 GET 요청을 작성하는 쉬운 방법을 간과해야한다는 것을 알고 있습니다.

현재 수동으로 "?"를 추가하고 있습니다. "&"로 결합 된 이름 값 쌍이 뒤 따르지만 모든 이름 및 값 쌍은 수동으로 인코딩해야 NSMutableURLRequest가 URL에 연결하려고 할 때 완전히 실패하지 않습니다.

이것은 내가 미리 구운 API를 사용할 수 있어야하는 것처럼 느껴집니다 .... NSURL에 쿼리 매개 변수의 NSDictionary를 추가 할 수있는 상자가 있습니까? 이에 접근해야하는 다른 방법이 있습니까?


너무 많은 답변! 질문과 답변에 대한 투표가 너무 많습니다! 그리고 여전히 대답으로 표시되지 않았습니다. 와!
BangOperator

답변:


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;

및 을 사용하여 URL을 작성하는 방법에 대한 블로그 게시물 도 작성했습니다 .NSURLComponentsNSURLQueryItems


2
사전이 중첩되면 어떻게됩니까?
hariszaman

1
URL을 통해 중첩 된 사전을 보내는 경우 @hariszaman 대신 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에 지정된대로 쿼리 매개 변수에서 이스케이프해야하는 기타 문자를 사용하고 있습니다 . 대신 Google Toolbox For Mac 이스케이프 코드를 살펴보십시오 .
Dave Peck

이것은 적절한 이스케이프 작동 : stackoverflow.com/questions/9187316/...
JoeCortopassi

30

Chris의 답변을 사용하고 싶었지만 ARC ( Automatic Reference Counting) 용으로 작성되지 않았 으므로 업데이트했습니다. 다른 사람이 이와 동일한 문제가있는 경우를 대비하여 내 솔루션을 붙여 넣을 것이라고 생각했습니다. 참고 :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'경우의 경우 길이 호출에 대한 호출에 barfing하지 않고 NSNumber에 대한 문자열을 제공하도록 설명에 대한 호출을 추가했습니다. '(CFStringRef) [[params objectForKey : key] description ]'감사합니다!
JohnQ

좋은 대답입니다. 그러나 CFURLCreateStringByAddingPercentEscapes통화를 다음으로 대체해야 합니다.stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]
S1LENT WARRIOR

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

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
꽤 깔끔하지만 queryItemsiOS8.0에서 사용할 수 있습니다.
Adil Soomro 2015

감사합니다.이를 명확히하기 위해 댓글을 추가했습니다.
Zorayr 2015

3

Joel의 권장 사항을 사용하여 URLQueryItemsSwift 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 dec.

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-04-23

나는 공간 +로 오히려 20 %에서 교체해야합니다 생각하지 않습니다이 변경 유일한 문자
Przemysław Wrzesiński

1

당신이 사용하는 경우 또 다른 해결책은 RestKit을 의 기능있다 RKURLEncodedSerialization라고 RKURLEncodedStringFromDictionaryWithEncoding정확하게 당신이 원하는 않는 것이다.


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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.