42

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

Mike Abdullah
  • 14,933
  • 2
  • 50
  • 75
Dave
  • 433
  • 1
  • 5
  • 7

13 Answers13

79

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.

Dimitris
  • 13,480
  • 17
  • 74
  • 94
  • 4
    +1 for awesomeness! `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. – Dave DeLong Feb 09 '10 at 02:28
  • I haven't played with `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. – Dimitris Feb 09 '10 at 09:51
  • Thanks for this reusable class – iOSAppDev May 28 '13 at 11:29
  • Have you created any opensource repository for this ? along with cocoapods ? – Janak Nirmal Jul 25 '13 at 06:32
  • Not working. `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: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key v.` – Doug Smith Dec 06 '13 at 18:29
  • Your answer has merit, but why re-invent the wheel? NSURLComponents is purpose-built for this. It knows how to break a URL into it's component parts, knows about query items, knows what parts need to be escaped/unescaped, and does it all for you. Plus it's tested code. @anders answer is the far better answer. – Duncan C Oct 27 '16 at 10:47
  • Thanks @DuncanC, I don't know if NSURLComponents existed back in 2010 when this was written. If Apple has now implemented this, it would definitely be the best approach. – Dimitris Dec 04 '16 at 13:16
  • Oh lordy this is an old thread! Looks like NSURLComponents was added in iOS 7, so I guess it wasn't available when you wrote your answer. – Duncan C Dec 04 '16 at 13:37
73

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

anders
  • 4,168
  • 2
  • 23
  • 31
  • 3
    Actually I don't understand why this answer is not marked as the corrected one. Thank you @anders. – Alexander Perechnev May 27 '16 at 14:34
  • 1
    @AlexKrzyżanowski, late to the game :P – anders May 27 '16 at 17:53
  • 1
    A little more compact: let pageNumber: Int? = Int(NSURLComponents(string: myURL.absoluteString ?? "")?.queryItems?.filter { $0.name == "page" }.first?.value ?? "NaN") – Edwin Vermeer Sep 30 '16 at 05:57
  • +1 on using NSURLComponents. (voted) It takes care of so much stuff for you. Much better than doing it yourself. – Duncan C Oct 28 '16 at 02:18
  • 1
    Edwin Vermeer and nefarianblack. Thanks for the suggestions however, while my code may be excessively verbose, can probably be optimized and can certainly be compacted, its MORE important for code examples to be as clear as possible. The same attitude should be followed in a professional setting. You should always focus on writing clean code that will not confuse your colleagues; its not a competition and its not beneficial to the productivity of the team if your code is unreadable at a short glance. Readability should ALWAYS be valued more than the compactness of your code. – anders Dec 16 '16 at 16:52
  • If you want something a little more concise in an if let you can do this: `if let queryItems = NSURLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems, let code = queryItems.filter({ $0.name == "page" }).reduce(nil, { $1 })?.value { /*Blah blah, do stuff with code variable here/* }` It's not quite as readable, but you could use named variables instead of anonymous vars if you wanted to make it a little more legible. I just thought this was helpful so you can get the result in an if-let. – stuckj Oct 30 '18 at 18:08
20

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"]);
Pang
  • 9,564
  • 146
  • 81
  • 122
Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • 8
    You should use `NSUTF8StringEncoding` rather that `NSASCIIStringEncoding` unless you hate non-English speakers. – Jakob Egger Apr 02 '13 at 11:32
  • He's right, y'know Dave, although it's a question of the encoding the URL scheme in question has decided to use. For virtually everything out there, that is UTF-8, happily enough – Mike Abdullah Jan 10 '14 at 11:20
11

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"])
leafcutter
  • 747
  • 7
  • 14
  • 1
    This should be the accepted answer now: the currently accepted one has been superseded. This approach isn't just for Swift either; Objective-C can use NSURLComponents too. It's available from iOS 7: http://nshipster.com/nsurl/ – SeanR May 12 '15 at 02:16
10

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&paramB=valueB"];
NSString *paramA = url[@"paramA"];
NSString *paramB = url[@"paramB"];
Troy
  • 21,172
  • 20
  • 74
  • 103
4

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)")
}
Albert Bori
  • 9,832
  • 10
  • 51
  • 78
4

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]];
ventayol
  • 635
  • 4
  • 16
3

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:

https://github.com/nicklockwood/RequestUtils

Nick Lockwood
  • 40,865
  • 11
  • 112
  • 103
2

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
odyth
  • 4,324
  • 3
  • 37
  • 45
  • +1: I agree that this is better suited for an `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... ;P – JRG-Developer Apr 02 '13 at 19:49
1

All 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;
}
IanS
  • 1,459
  • 1
  • 18
  • 23
0

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

tryp
  • 1,120
  • 20
  • 26
0

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
Chilly
  • 743
  • 8
  • 7
-2

Quickest is:

NSString* x = [url valueForQueryParameterKey:@"x"];
sth
  • 222,467
  • 53
  • 283
  • 367