That's what I would recommend.
The Foundation URL model supports a lot of things (not even talking about the parser - just the object model). It supports absolute URLs, relative references, and multiple representations of each (resolved, and split baseURL/relativeString
).
This is what I mean by the split representation:
import Foundation
let url = URL(string: "b/c", relativeTo: URL(string: "http://example.com/a/")!)!
// The URL components appear to be joined.
url.host // "example.com"
url.path // "/a/b/c"
// But underneath, the parts are stored separately.
url.relativeString // "b/c"
url.baseURL // "http://example.com/a/"
This amount of complexity is difficult to manage - if a user of the API constructs a URL in the wrong way (or slightly changes it during an apparently harmless refactoring), it may not behave as they expect it to:
import Foundation
let urlA = URL(string: "http://example.com/a/b/c")!
let urlB = URL(string: "/a/b/c", relativeTo: URL(string: "http://example.com")!)!
let urlC = URL(string: "b/c", relativeTo: URL(string: "http://example.com/a/")!)!
// All of these URLs have the same .absoluteString.
urlA.absoluteString == urlB.absoluteString // true
urlB.absoluteString == urlC.absoluteString // true
// But they are not interchangeable.
urlA == urlB // false (!)
urlB == urlC // false (!)
URL(string: urlB.absoluteString) == urlB // false (!)
// Let's imagine an application using URLs as keys in a dictionary:
var operations: [URL: TaskHandle] = [:]
operations[urlA] = TaskHandle { ... }
operations[urlA] // TaskHandle
operations[urlB] // nil (!)
If a library expects a particular baseURL
, any code which needs to satisfy that requirement needs work very delicately.
Some APIs and operations will just drop the baseURL
, and it isn't even documented which of them do that, and the API doesn't include alternatives for all operations which preserve baseURL
(some of them are not even possible) -- if you need an operation, then discover when running that it drops the baseURL
, but your library expects a particular baseURL
, you might just be stuck.
For example:
let base = URL(string: "http://example.com")!
var urlA = URL(string: "/a/b/c", relativeTo: base)!
assert(urlA.baseURL == base)
urlA.append(queryItems: [URLQueryItem(name: "test", value: "foo")])
assert(urlA.baseURL == nil) // ?! But what's my alternative?
So, the baseURL
feature:
- Doesn't work very reliably
- Shouldn't become part of your API contract ("this parameter is a URL whose baseURL is..." )
- Costs a lot in complexity
I've been meaning to pitch this to Foundation at some point. There may be a path forward where we just deprecate the baseURL
and relativeString
properties, and have the initialisers eagerly resolve the relative string against the base URL in a new SDK version. If you're not using the baseURL
and relativeString
properties, you shouldn't (in theory) notice the difference, except for one thing:
I think that change would be enough to finally give Foundation URL the property that if two URLs have the same absolute string, they compare as ==
, so the above example with URLs as dictionary keys would work as you expect and not be so fragile.
That's why I think removing/deprecating these properties would be the single biggest improvement we could make to Foundation's URL API.
Anyway, sorry, I went off a bit. It’s rare that somebody asks about these things, so it’s an opportunity to give some broader advice based on the time I’ve spent investigating and designing URL interfaces. To answer your question, try FilePath
. removePrefix
looks like it might help if you know you're dealing with subpaths:
var path: FilePath = "/usr/local/bin"
path.removePrefix("/usr/bin") // false
path.removePrefix("/us") // false
path.removePrefix("/usr/local") // true, path is "bin"