Foundation’s URL (née NSURL) is a nearly ubiquitous API on Apple platforms. One of its shortcomings is that it is heavily overloaded – an instance of URL could represent a web URL or a file URL. While there are many similarities between accessing resources on a local disk or on a web server, I think there should be explicit types for each, say WebURL and FileURL.

What makes URL more confusing is how other APIs often interchangeably use String and URL or provide multiple APIs with the same functionality where some use URL and others use String. The best example of this is FileManager, which can’t seem to decide on one or the other. You can construct a URL from a single String or build a URL incrementally from multiple String values, and you can convert a URL back to a String. Also when working with networking APIs, it is common to move back and forth between String and URL.

Even after years of working with Foundation on Apple platforms, I make the same mistake using URL with files all the time. When you are working with networking code and you need a string representation of a URL, you need to call URL.absoluteString. This is so common and it is always the first thing I reach for — it even has “string” in the name! But when working with file URLs and FileManager, this is not what you want.

I was recently writing some code dealing with files using FileManager and, out of habit, I tried to pass URL.absoluteString to an API that needed a String file path. It took way too long for me to figure out the bug, because I am so used to seeing and using absoluteString. It was not an obvious error.

For file URLs, URL.absoluteString will produce:

file:///Users/jsq/Documents/file.txt

Note that the string includes the file:// scheme. For file-based APIs that require a String file path, passing the value returned by absoluteString will fail.

However, URL.path will produce the full file path without the file:// scheme prefix.

/Users/jsq/Documents/file.txt

When working with file URLs, the correct way to convert to a String value is to use URL.path. This is confusing, because URL.path for web URLs means something very different. And this helps illustrate the problem with the overloaded behavior of URL — many of the APIs behave differently depending on the type of URL you have. Even more confusing, some APIs do not make sense to include for both kinds of URLs. For example, URL.host and URL.query do not apply to file URLs.

Of course, URL.isFileURL exists to allow you to distinguish between the two if needed, but this further exposes the poor design of URL and emphasizes the need for two explicit types. Using FileManager would be much more intuitive if all of its APIs worked with a single FileURL type instead of mixing String and URL.