Tuesday, December 9, 2008

UrlEncoding category for NSDictionary class

I've been writing some Objective-C code that builds URLs with long query strings. Since query strings are almost always associative arrays, I whipped up a simple category for the NSDictionary class.

A category is the Objective-C mechanism for adding additional methods to an existing class. To a C++ programmer, it's like being able to append more virtual methods to a class's vtable. C# has a similar facility called extension methods, and there are proposals to add this to Java. Of course, this is no big deal for scripting languages like Ruby, Python or JavaScript, but I like how Objective-C handles this.

For one thing, Objective-C forces you to name your category; this helps a lot in thinking and talking about a set of additional methods. For another, since the category declaration syntax resembles class declaration syntax, you naturally want to place it in its own pair of .h and .m files, like a class. Apple recommends a naming convention of class+category.h/m, which seems pretty reasonable.

So here's the header file for my UrlEncoding category for NSDictionary:
// file "NSDictionary+UrlEncoding.h"

#import <cocoa/cocoa.h>


@interface NSDictionary (UrlEncoding)

-(NSString*) urlEncodedString;

@end


And here's the module file:
// 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

The code is pretty simple. Iterate over the keys in the dictionary and build an array of URL encoded "name=value" parts, then join the parts together with ampersands. The urlEncodedString method will handle keys and values of any class as long it has a reasonable description method. The code assumed that you never want a name/key to appear more than once in the query string, which is the most common case.

5 comments:

Jeff said...

How would you use this? (i.e. How would you add it to a project?)

Don McCaughey said...

Unfortunately Xcode doesn't include a template for creating categories, so you can either use Add | New File | Cocoa Touch Class | Objective-C Class template to create the NSDictionary+UrlEncoding.h and .m files, then replace the template-generated code or use Add | New File | Other | Empty File twice to create the .h and .m and paste the code in the respective files.

DavidPhillipOster said...

If you look in http://code.google.com/p/google-toolbox-for-mac specifically http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundation/GTMNSDictionary%2BURLArguments.h you'll find another implementation of this idea.

steve said...

Hey,

Thank you for sharing such a nice and neat piece of code.

I am wondering what license you are releasing this under, as I can see people either a) not using it due to legal ambiguity or b) using without attribution due to licensing concerns.

Cheers,
Steve

Mr.Black said...

hi...I think you should add one exception.
Dealing with "white space", %20 --> +.

Anyway, great thanks for your great work!!