With Xcode 14.3.1 (Swift 5.8.1) I was able to convert URL(string: "127.0.0.1:8000/test")
into a valid URL. With Xcode 15.0 Beta 5 (Swift 5.9) the same initializer returns nil
. Is this a new behavior or a bug? Does Swift 5.9 already use a pre version of the new open source Foundation package, which already includes URL
?
I wonder if this is really a valid URL.
It is not defined what kind of URL is used, e.g.
URL(string: "http://127.0.0.1:8000/test")
The documentation for URL is not really helpful but at NSURL " init(string:)" is some more information:
URLString
The URL string with which to initialize the NSURL object. This URL string must conform to URL format as described in RFC 2396, and must not be
nil
. This method parsesURLString
according to RFCs 1738 and 1808.
Just out of curiosity - how were you using the resulting URL value before? Because there is no scheme, there is no way to know what the string means, and indeed the reported components are not what a human would expect:
let url = URL(string: "127.0.0.1:8000/test")!
url.host // nil
url.port // nil
url.path // "127.0.0.1:8000/test"
You also can't make a request to it:
// ❌ throws error: "Unsupported URL"
for try await l in URL(string: "127.0.0.1:8000/test")!.lines {
print(l)
}
Don't get me wrong - compatibility is very important and behaviour changes need to be made very carefully. But I also wonder what was left that you could do with these URL values.
Note to avoid potential confusion that URL
is a type in a framework, and so its implementation ships in the operating system. So any change in this behavior would be related to the OS you are running the binary on (i.e. macOS Sonoma), not the version of Swift or Xcode you have.
(an exception to this, which I don't think applies here, is if the initializer were inlinable, in which case it would be related to the version of the SDK you are using, which is tied to Xcode)
Part of the confusion is the the change was originally noted in the Xcode 15 beta 1 release notes rather than any particular OS.
Fixed: For apps linked on or after iOS 17 and aligned OS versions,
URL
parsing has updated from the obsolete RFC 1738/1808 parsing to the same RFC 3986 parsing asURLComponents
. This unifies the parsing behaviors of theURL
andURLComponents
APIs. Now,URL
automatically percent- or IDNA-encodes invalid characters to help create a valid URL.To check if a
urlString
is strictly valid according to the RFC, use the newURL(string: urlString, encodingInvalidCharacters: false)
initializer. This init leaves all characters as they are and will returnnil
ifurlString
is explicitly invalid. (93368104)
Granted, Apple doesn't have a good alternate place to put changes like this without duplicating it in every OS' release notes (which has happened), but some confusion is likely when it shows up in the Xcode release notes itself.
So...passing invalid strings on iOS15~16 will always succeed once encodingInvalidCharacters
is only available on iOS17?
I see that in Xcode 15.0.1 passing invalid strings to iOS15~16 has the original behaviour: it will fail when passing invalid characters.
Yes, this behavior isn't part of the compiler but the version of the SDK you're running on. Definitely an instance where you need to keep your old escaping logic around conditionally. But it is confusing since the compiler doesn't tell you anything different with an older deployment target. You could also use the new URL(string:encodingInvalidCharacters:)
init on iOS 17 to match the older behavior.