I have an NSURL:
serverCall?x=a&y=b&z=c
What is the quickest and most efficient way to get the value of y?
Thanks
UPDATE:
Since 2010 when this was written, it seems Apple has released a set of tools for that purpose. Please see the answers below for those.
Old-School Solution:
Well I know you said "the quickest way" but after I started doing a test with NSScanner I just couldn't stop. And while it is not the shortest way, it is sure handy if you are planning to use that feature a lot. I created a URLParser class that gets these vars using an NSScanner. The use is a simple as:
URLParser *parser = [[[URLParser alloc] initWithURLString:@"http://blahblahblah.com/serverCall?x=a&y=b&z=c&flash=yes"] autorelease];
NSString *y = [parser valueForVariable:@"y"];
NSLog(@"%@", y); //b
NSString *a = [parser valueForVariable:@"a"];
NSLog(@"%@", a); //(null)
NSString *flash = [parser valueForVariable:@"flash"];
NSLog(@"%@", flash); //yes
And the class that does this is the following (*source files at the bottom of the post):
URLParser.h
@interface URLParser : NSObject {
NSArray *variables;
}
@property (nonatomic, retain) NSArray *variables;
- (id)initWithURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;
@end
URLParser.m
@implementation URLParser
@synthesize variables;
- (id) initWithURLString:(NSString *)url{
self = [super init];
if (self != nil) {
NSString *string = url;
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"&?"]];
NSString *tempString;
NSMutableArray *vars = [NSMutableArray new];
[scanner scanUpToString:@"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:@"&" intoString:&tempString]) {
[vars addObject:[tempString copy]];
}
self.variables = vars;
[vars release];
}
return self;
}
- (NSString *)valueForVariable:(NSString *)varName {
for (NSString *var in self.variables) {
if ([var length] > [varName length]+1 && [[var substringWithRange:NSMakeRange(0, [varName length]+1)] isEqualToString:[varName stringByAppendingString:@"="]]) {
NSString *varValue = [var substringFromIndex:[varName length]+1];
return varValue;
}
}
return nil;
}
- (void) dealloc{
self.variables = nil;
[super dealloc];
}
@end
*if you don't like copying and pasting you can just download the source files - I made a quick blog post about this here.
NSScanner is a class that I haven't played with much, and this looks really interesting. The only comment I'd say is to not call the method getValue.... That implies (according to convention) that you're going to be returning the value via an out parameter. valueForVariable: would be the proper name.NSScanner before either so I figured this a nice task to test it with. As for the method name, I didn't like it either but it was 2:00am and wanted to wrap things up :P It's updated.https://www.youtube.com/watch?feature=player_embedded&v=Bci1eZFoyEg crashes it every time with this error: *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<DDURLParser 0x8d37bf0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key v.So many custom url parsers here, remember NSURLComponents is your friend!
Here is an example where I pull out a url encoded parameter for "page"
Swift
let myURL = "www.something.com?page=2"
var pageNumber : Int?
if let queryItems = NSURLComponents(string: myURL)?.queryItems {
for item in queryItems {
if item.name == "page" {
if let itemValue = item.value {
pageNumber = Int(itemValue)
}
}
}
}
print("Found page number: \(pageNumber)")
Objective-C
NSString *myURL = @"www.something.com?page=2";
NSURLComponents *components = [NSURLComponents componentsWithString:myURL];
NSNumber *page = nil;
for(NSURLQueryItem *item in components.queryItems)
{
if([item.name isEqualToString:@"page"])
page = [NSNumber numberWithInteger:item.value.integerValue];
}
"Why reinvent the wheel!" - Someone Smart
I'm pretty sure you have to parse it yourself. However, it's not too bad:
NSString * q = [myURL query];
NSArray * pairs = [q componentsSeparatedByString:@"&"];
NSMutableDictionary * kvPairs = [NSMutableDictionary dictionary];
for (NSString * pair in pairs) {
NSArray * bits = [pair componentsSeparatedByString:@"="];
NSString * key = [[bits objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString * value = [[bits objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[kvPairs setObject:value forKey:key];
}
NSLog(@"y = %@", [kvPairs objectForKey:@"y"]);
NSUTF8StringEncoding rather that NSASCIIStringEncoding unless you hate non-English speakers.In Swift you can use NSURLComponents to parse the query string of an NSURL into an [AnyObject].
You can then create a dictionary from it (or access the items directly) to get at the key/value pairs. As an example this is what I am using to parse a NSURL variable url:
let urlComponents = NSURLComponents(URL: url, resolvingAgainstBaseURL: false)
let items = urlComponents?.queryItems as [NSURLQueryItem]
var dict = NSMutableDictionary()
for item in items{
dict.setValue(item.value, forKey: item.name)
}
println(dict["x"])
I've been using this Category: https://github.com/carlj/NSURL-Parameters.
It's small and easy to use:
#import "NSURL+Parameters.h"
...
NSURL *url = [NSURL URLWithString:@"http://foo.bar.com?paramA=valueA¶mB=valueB"];
NSString *paramA = url[@"paramA"];
NSString *paramB = url[@"paramB"];
You can use Google Toolbox for Mac. It adds a function to NSString to convert query string to a dictionary.
http://code.google.com/p/google-toolbox-for-mac/
It works like a charm
NSDictionary * d = [NSDictionary gtm_dictionaryWithHttpArgumentsString:[[request URL] query]];
Here's a Swift 2.0 extension that provides simple access to parameters:
extension NSURL {
var params: [String: String] {
get {
let urlComponents = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)
var items = [String: String]()
for item in urlComponents?.queryItems ?? [] {
items[item.name] = item.value ?? ""
}
return items
}
}
}
Sample usage:
let url = NSURL(string: "http://google.com?test=dolphins")
if let testParam = url.params["test"] {
print("testParam: \(testParam)")
}
I wrote a simple category to extend NSString/NSURL that lets you extract URL query parameters individually or as a dictionary of key/value pairs:
I did it using a category method based on @Dimitris solution
#import "NSURL+DictionaryValue.h"
@implementation NSURL (DictionaryValue)
-(NSDictionary *)dictionaryValue
{
NSString *string = [[self.absoluteString stringByReplacingOccurrencesOfString:@"+" withString:@" "]
stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"&?"]];
NSString *temp;
NSMutableDictionary *dict = [[[NSMutableDictionary alloc] init] autorelease];
[scanner scanUpToString:@"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:@"&" intoString:&temp])
{
NSArray *parts = [temp componentsSeparatedByString:@"="];
if([parts count] == 2)
{
[dict setObject:[parts objectAtIndex:1] forKey:[parts objectAtIndex:0]];
}
}
return dict;
}
@end
NSDictionary than NSArray. However, if you're going to use this function, make sure that you're assigning this value to an ivar... i.e. NSDictionary *paramsDict = [myURL dictionaryValue]... you don't want to build this dictionary over and over to get each parameter's value... ;PAll of the current answers are version specific or needlessly wasteful. Why create a dictionary if you only want one value?
Here's a simple answer that supports all iOS versions:
- (NSString *)getQueryParam:(NSString *)name fromURL:(NSURL *)url
{
if (url)
{
NSArray *urlComponents = [url.query componentsSeparatedByString:@"&"];
for (NSString *keyValuePair in urlComponents)
{
NSArray *pairComponents = [keyValuePair componentsSeparatedByString:@"="];
NSString *key = [[pairComponents firstObject] stringByRemovingPercentEncoding];
if ([key isEqualToString:name])
{
return [[pairComponents lastObject] stringByRemovingPercentEncoding];
}
}
}
return nil;
}
You can do that easy :
- (NSMutableDictionary *) getUrlParameters:(NSURL *) url
{
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
NSString *tmpKey = [url query];
for (NSString *param in [[url query] componentsSeparatedByString:@"="])
{
if ([tmpKey rangeOfString:param].location == NSNotFound)
{
[params setValue:param forKey:tmpKey];
tmpKey = nil;
}
tmpKey = param;
}
[tmpKey release];
return params;
}
It return Dictionary like it : Key = value
I edited Dimitris' code slightly for better memory management and efficiency. Also, it works in ARC.
URLParser.h
@interface URLParser : NSObject
- (void)setURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;
@end
URLParser.m
#import "URLParser.h"
@implementation URLParser {
NSMutableDictionary *_variablesDict;
}
- (void)setURLString:(NSString *)url {
[_variablesDict removeAllObjects];
NSString *string = url;
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"&?"]];
NSString *tempString;
[scanner scanUpToString:@"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:@"&" intoString:&tempString]) {
NSString *dataString = [tempString copy];
NSArray *sepStrings = [dataString componentsSeparatedByString:@"="];
if ([sepStrings count] == 2) {
[_variablesDict setValue:sepStrings[1] forKeyPath:sepStrings[0]];
}
}
}
- (id)init
{
self = [super init];
if (self) {
_variablesDict = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSString *)valueForVariable:(NSString *)varName {
NSString *val = [_variablesDict valueForKeyPath:varName];
return val;
return nil;
}
-(NSString *)description {
return [NSString stringWithFormat:@"Current Variables: %@", _variablesDict];
}
@end