在 Swift 中使用 URLComponents 对“+”进行编码

Encode '+' using URLComponents in Swift

这就是我向基础添加查询参数的方式 URL:

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

当其中一个值包含 + 符号时,就会出现问题。由于某种原因,它没有在最终 URL 中编码为 %2B,而是保持 +。如果我自己编码并传递 %2BNSURL 编码 % 并且 'plus' 变为 %252B.

问题是如何在 NSURL 的实例中得到 %2B

P.S。我知道,如果我自己构造一个查询字符串然后简单地将结果传递给 NSURL 的构造函数 init?(string:).

,我什至不会遇到这个问题

你可以尝试使用 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: ([=10=]), value: "\()") }
let finalURL = components?.url

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

只是一些改进。如果 一个字符串,OP 的 value: "\()" 是不必要的;你可以直接说 value:。而且,最好由所有组件组成 URL。

因此,这与 Forest Kunecke 的解决方案本质上是相同的,但我认为它更规范,而且最终肯定更紧凑:

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: [=10=], 
    value: .addingPercentEncoding(withAllowedCharacters: .alphanumerics)!) 
}
let finalURL = components.url

EDIT 更好,也许,在 Martin R 建议的更正之后:我们形成整个查询并自己对片段进行百分比编码,然后告诉 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 {
    [=11=].addingPercentEncoding(withAllowedCharacters: cs)! + 
    "=" + 
    .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

正如其他答案中所指出的,“+”字符在 一个查询字符串,这也在 query​Items 文档:

According to RFC 3986, the plus sign is a valid character within a query, and doesn't need to be percent-encoded. However, according to the W3C recommendations for URI addressing, the plus sign is reserved as shorthand notation for a space within a query string (for example, ?greeting=hello+world).
[...]
Depending on the implementation receiving this URL, you may need to preemptively percent-encode the plus sign character.

还有 W3C recommendations for URI addressing 说明

Within the query string, the plus sign is reserved as shorthand notation for a space. Therefore, real plus signs must be encoded. This method was used to make query URIs easier to pass in systems which did not allow spaces.

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

let queryParams = ["foo":"a+b", "bar": "a-b", "baz": "a b"]
var components = URLComponents()

var cs = CharacterSet.urlQueryAllowed
cs.remove("+")

components.scheme = "http"
components.host = "www.example.com"
components.path = "/somepath"
components.percentEncodedQuery = queryParams.map {
    [=10=].addingPercentEncoding(withAllowedCharacters: cs)!
    + "=" + .addingPercentEncoding(withAllowedCharacters: cs)!
}.joined(separator: "&")

let finalURL = components.url
// http://www.example.com/somepath?bar=a-b&baz=a%20b&foo=a%2Bb

另一种选择是“post-encode”生成的加号 百分比编码的查询字符串:

let queryParams = ["foo":"a+b", "bar": "a-b", "baz": "a b"]
var components = URLComponents()
components.scheme = "http"
components.host = "www.example.com"
components.path = "/somepath"
components.queryItems = queryParams.map { URLQueryItem(name: [=11=], value: ) }
components.percentEncodedQuery = components.percentEncodedQuery?
    .replacingOccurrences(of: "+", with: "%2B")

let finalURL = components.url
print(finalURL!)
// http://www.example.com/somepath?bar=a-b&baz=a%20b&foo=a%2Bb

您可以在插入查询项后简单地编码 components.percentEncodedQuery

let characterSet = CharacterSet(charactersIn: "/+").inverted
components.percentEncodedQuery = components.percentEncodedQuery?.addingPercentEncoding(withAllowedCharacters: characterSet)