views:

483

answers:

2

Playing with URLs, more specifically building them incrementally from other, discovered URLs. In doing so, I'd like to stay using NSURL objects instead of manipulating NSStrings, just to gain the added sanity checks and url-specific methods from the URL class.

Unfortunately, it seems that there is no way to get the following to join together how I wish:

NSURL *base = [NSURL URLWithString:@"http://my.url/path"];
NSString *suffix = @"sub/path";

I want to append them to get:

http://my.url/path/sub/path

But the best I can seem to get is:

NSURL *final = [NSURL URLWithString:suffix relativeToURL:base];

Which trims off the path on the base, resulting in:

http://my.url/sub/path

There's a CoreFoundation function that does it:

CFURLRef CFURLCreateCopyAppendingPathComponent (
   CFAllocatorRef allocator,
   CFURLRef url,
   CFStringRef pathComponent,
   Boolean isDirectory
);

Which seems to work fine, but bouncing back/forth from ObjC to C is jarring and annoying... I'd rather just manipulate strings... Am I missing something? Other than being way too picky of course. :)

+1  A: 

You could convert the URL to a string, append your relative component, then convert it back to a URL:

NSURL *base = [NSURL URLWithString:@"http://my.url/path"];
NSString *suffix = @"/sub/path"; //Note that you need the initial forward slash

NSString *newURLString = [[base absoluteString] stringByAppendingString:suffix];
NSURL *newURL = [NSURL URLWithString:newURLString];

Side note: If the URL happens to be to a file (not in your initial example, but it may help at some point), you can use NSString's stringByAppendingPathComponent: method as well - this isolates you from picking the right separator for your base and relative paths.

Tim
This is a pretty bad idea as you'd have to handle processing of slashes exactly right yourself. Plus a URL with anything after the path (query string etc.) would be horribly mangled.
Mike Abdullah
+4  A: 

This is Objective C - if NSURL does not do what you need, add an extension category.

@interface NSURL ( Extensions )
- (NSURL*) urlByAppendingPathComponent: (NSString*) component;
@end

@implementation NSURL ( Extensions )
- (NSURL*) urlByAppendingPathComponent: (NSString*) component;
{
    CFURLRef newURL = CFURLCreateCopyAppendingPathComponent( kCFAllocatorDefault, (CFURLRef)[self absoluteURL], (CFStringRef)component, [component hasSuffix:@"/"] );
    return [NSMakeCollectable(newURL) autorelease];
}
@end

and then:

*base = [NSURL URLWithString:@"http://my.url/path"];
*suffix = @"sub/path";
NSURL *final = [base urlByAppendingPathComponent:suffix];
NSLog( @"%@", final );
// displays http://my.url/path/sub/path
Peter N Lewis
One thing to watch out for is how CoreFoundation handles relative URLs (those with both a -baseURL and -relativeString). You probably want to do (CFURLRef)[self absoluteURL] instead.
Mike Abdullah
Nice, that'll work. Thanks.
Nick Veys
Good suggestion Mike, I made the adjustment
Peter N Lewis
+1 — An excellent suggestion. If I may nitpick slightly, perhaps choosing a more specific category name than "Extensions" might help the clarity.
Quinn Taylor