How do you build, change and resolve URL paths safely?
Predict first
What does this print?
let base = URL(string: "https://x.com/a/b/")!
print(URL(string: "../c", relativeTo: base)!.absoluteString)
- https://x.com/a/c
- https://x.com/a/b/c
- https://x.com/c
Predict first
What does this print?
print(URL(string: "https://x.com/a/b")!.deletingLastPathComponent().absoluteString)
- https://x.com/a
- https://x.com/a/
Mental model. Path components are steps along a corridor. The trailing slash tells you whether the last step is a room or a door.
Edit paths with the URL APIs, not string slicing: appendingPathComponent(_:) (or appending(path:)) adds a segment and percent-encodes it, deletingLastPathComponent() removes one, and pathExtension / deletingPathExtension() handle the suffix. standardized collapses . and ...
A URL can also be relative to a base: URL(string: "../c", relativeTo: base). Resolution follows the browser rules, so a trailing slash on the base matters: /a/b/ is a folder, /a/b is a file named b, and the same relative link lands in different places. Call absoluteURL before storing a relative URL.
.. climbs out of the folder b/, so the link resolves next to it.
The result keeps a trailing slash, marking it as a folder.
Common traps
- Concatenating
"/"by hand. You get double slashes or missing ones, and no encoding. - Storing a relative URL. It loses its base on encoding in some paths. Convert with
absoluteURLfirst.
They might follow up with
- How do you swap a file extension?
url.deletingPathExtension().appendingPathExtension("png"). - Why does
appendingPathComponentadd a slash for folders? It asks the disk, for file URLs, unless you passisDirectory:.
Related questions
- 2.3.2 What is the difference between a file URL and a web URL, and URL(fileURLWithPath:) and URL(string:)?
- 2.3.4 Why use URLComponents and URLQueryItem, and what do they still not encode for you?
← What is the difference between a file URL and a web URL, and… Why use URLComponents and URLQueryItem, and what do they still not… →