在Swift中使用URLComponents编码“+”

这是我如何添加一个查询参数到一个基本的URL:

let baseURL: URL = ... let queryParams: [AnyHashable: Any] = ... var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) components?.queryItems = queryParams.map { URLQueryItem(name: $0, value: "\($1)") } let finalURL = components?.url 

如果其中一个值包含+符号,则会出现问题。 出于某种原因,它不会被编码到最终URL中的%2B ,而是保持+ 。 如果我自己编码并通过%2BNSURL编码% ,'加号'变成%252B

问题是我怎么能在NSURL的实例%2B

PS我知道,我甚至不会有这个问题,如果我自己构造一个查询string,然后只是将结果传递给NSURL的构造函数init?(string:)

正如在其他答案中指出的那样,“+”字符在查询string中是有效的,这也在query​Items文档中说明。

另一方面, W3C对URI寻址状态的build议是

在查询string中,加号被保留为空格的简写符号。 因此,真正的加号必须被编码。 这个方法被用来使查询URI更容易在不允许空格的系统中通过。

这可以通过使用自定义字符集“手动”构build百分比编码查询string来实现:

 let queryParams = ["foo":"a+b", "bar": "ab", "baz": "ab"] var components = URLComponents() var cs = CharacterSet.urlQueryAllowed cs.remove("+") components.scheme = "http" components.host = "www.example.com" components.path = "/somepath" components.percentEncodedQuery = queryParams.map { $0.addingPercentEncoding(withAllowedCharacters: cs)! + "=" + $1.addingPercentEncoding(withAllowedCharacters: cs)! }.joined(separator: "&") let finalURL = components.url // http://www.example.com/somepath?bar=ab&baz=a%20b&foo=a%2Bb 

另一种select是在生成的百分比编码的查询string中对加号进行“后编码”:

 let queryParams = ["foo":"a+b", "bar": "ab", "baz": "ab"] var components = URLComponents() components.scheme = "http" components.host = "www.example.com" components.path = "/somepath" components.queryItems = queryParams.map { URLQueryItem(name: $0, value: $1) } components.percentEncodedQuery = components.percentEncodedQuery? .replacingOccurrences(of: "+", with: "%2B") let finalURL = components.url print(finalURL!) // http://www.example.com/somepath?bar=ab&baz=a%20b&foo=a%2Bb 

你可以尝试使用addingPercentEncoding(withAllowedCharacters: .alphanumerics)

我只是把一个快速的操场放在一起,说明这是如何工作的:

 //: Playground - noun: a place where people can play let baseURL: URL = URL(string: "http://example.com")! let queryParams: [AnyHashable: Any] = ["test": 20, "test2": "+thirty"] var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) var escapedComponents = [String: String]() for item in queryParams { let key = item.key as! String let paramString = "\(item.value)" // percent-encode any non-alphanumeric character. This is NOT something you typically need to do. User discretion advised. let escaped = paramString.addingPercentEncoding(withAllowedCharacters: .alphanumerics) print("escaped: \(escaped)") // add the newly escaped components to our dictionary escapedComponents[key] = escaped } components?.queryItems = escapedComponents.map { URLQueryItem(name: ($0), value: "\($1)") } let finalURL = components?.url 

URLComponents行为正确: +不是百分比编码,因为它是合法的。 您可以使用.alphanumerics 强制 +编码,正如Forest Kunecke已经解释的那样(我独立获得了相同的结果,但他在提交答案时远远领先于我)。

只是一些改进。 OP的value: "\($1)"是不必要的,如果这一个string; 你可以说value:$1 。 而且,从所有组件构buildURL会更好。

因此,这与森林Kunecke基本上是一样的解决scheme,但我认为它更加规范,最终确实更加紧凑:

 let queryParams = ["hey":"ho+ha"] var components = URLComponents() components.scheme = "http" components.host = "www.example.com" components.path = "/somepath" components.queryItems = queryParams.map { URLQueryItem(name: $0, value: $1.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!) } let finalURL = components.url 

编辑更好,也许,在马丁Rbuild议更正后:我们形成整个查询和百分比编码我们自己,并告诉URLComponents,我们这样做:

 let queryParams = ["hey":"ho+ha", "yo":"de,ho"] var components = URLComponents() components.scheme = "http" components.host = "www.example.com" components.path = "/somepath" var cs = CharacterSet.urlQueryAllowed cs.remove("+") components.percentEncodedQuery = queryParams.map { $0.addingPercentEncoding(withAllowedCharacters: cs)! + "=" + $1.addingPercentEncoding(withAllowedCharacters: cs)! }.joined(separator:"&") // ---- Okay, let's see what we've got ---- components.queryItems // [{name "hey", {some "ho+ha"}}, {name "yo", {some "de,ho"}}] components.url // http://www.example.com/somepath?hey=ho%2Bha&yo=de,ho