HTTPURLResponse allHeaderFields Swift 3 大小写
HTTPURLResponse allHeaderFields Swift 3 Capitalisation
转换为 Swift 3 我注意到从 HTTPURLResponse 读取 header 字段时出现了一个奇怪的错误。
let id = httpResponse.allHeaderFields["eTag"] as? String
不再有效。
我打印了所有 header 的字典,我所有的 header 键似乎都是句子大小写。
根据 Charles 代理,我所有的 header 都是小写的。根据后端团队的说法,在他们的代码中 header 位于 Title-Case 中。根据文档:headers 应该是 case-insensitive。
所以我不知道该相信哪个。是否还有其他人在 Swift 3 中发现他们的 header 现在被 iOS 变成了 Sentence case?如果是这样,这是我们想要的行为吗?
我应该用 Apple 记录一个错误,还是应该在 HTTPURLResponse 上创建一个类别,让我自己不区分大小写地找到一个 header 值。
更新:这是一个known issue.
allHeaderFields
应该 return 一个不区分大小写的字典,因为这是 HTTP 规范所要求的。看起来像 Swift 错误,我会在 .
上提交雷达或错误报告
下面是一些简单重现问题的示例代码:
let headerFields = ["ETag" : "12345678"]
let url = URL(string: "http://www.example.com")!
let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: headerFields)!
response.allHeaderFields["eTaG"] // nil (incorrect)
headerFields["eTaG"] // nil (correct)
(改编自 this Gist from Cédric Luthi。)
作为 Swift 3 的修补程序,您可以这样做:
// Converting to an array of tuples of type (String, String)
// The key is lowercased()
let keyValues = response.allHeaderFields.map { (String(describing: [=10=].key).lowercased(), String(describing: [=10=].value)) }
// Now filter the array, searching for your header-key, also lowercased
if let myHeaderValue = keyValues.filter({ [=10=].0 == "X-MyHeaderKey".lowercased() }).first {
print(myHeaderValue.1)
}
更新:此问题似乎仍未在 Swift 4.
中修复
我点击了这个并使用 Dictionary
上的扩展来解决它来创建自定义下标。
extension Dictionary {
subscript(key: String) -> Value? {
get {
let anyKey = key as! Key
if let value = self[anyKey] {
return value // 1213ns
}
if let value = self[key.lowercased() as! Key] {
return value // 2511ns
}
if let value = self[key.capitalized as! Key] {
return value // 8928ns
}
for (storedKey, storedValue) in self {
if let stringKey = storedKey as? String {
if stringKey.caseInsensitiveCompare(key) == .orderedSame {
return storedValue // 22317ns
}
}
}
return nil
}
set {
self[key] = newValue
}
}
}
评论中的时间来自不同场景的基准测试(优化构建,-Os
,平均超过 1,000,000 次迭代)。标准字典的等效访问在 1257ns 时出现。必须进行两次检查有效地使它翻了一番,2412ns。
在我的特殊情况下,我看到 header 从 camel-case 或 lower-case 的服务器返回,具体取决于我连接的网络(其他去弄清楚)。这样做的好处是,如果它得到修复,我可以删除扩展而无需更改其他任何内容。此外,使用该代码的其他任何人都不需要记住任何解决方法 - 他们可以免费获得这个。
我检查了一下,没有看到 ETag
被 HTTPURLResponse
修改了 - 如果我通过了它 ETag
,或者 Etag
我在 [=18= 中得到了那些].如果性能是一个问题,并且您遇到了这个问题,您可以创建第二个下标,它采用包含数组的 Hashable
结构。然后将其传递给 Dictionary,其中包含您要处理的标签。
struct DictionaryKey: Hashable {
let keys: [String]
var hashValue: Int { return 0 } // Don't care what is returned, not going to use it
}
func ==(lhs: DictionaryKey, rhs: DictionaryKey) -> Bool {
return lhs.keys == rhs.keys // Just filling expectations
}
extension Dictionary {
subscript(key: DictionaryKey) -> Value? {
get {
for string in key.keys {
if let value = self[string as! Key] {
return value
}
}
return nil
}
}
}
print("\(allHeaderFields[DictionaryKey(keys: ["ETag", "Etag"])])"
如您所料,这几乎等同于进行单独的字典查找。
有一个 little-bit 比 Darko 的 swift 3.0 版本更短的版本。
由于 header 名称在 iOS8 和 iOS10 中的大小写可能不同,因此最好的方法是使用不区分大小写的比较。
response.allHeaderFields.keys.contains(where: {[=10=].description.caseInsensitiveCompare("CaSe-InSeNsItIvE-HeAdEr") == .orderedSame})
所以现在支持所有类型:
- case-insensitive-header
- Case-Insensitive-Header
- CASE-INSENSITIVE-HEADER
根据@Darko 的回答,我做了一个 Swift 3
扩展,可以让您找到不区分大小写的任何 headers:
import Foundation
extension HTTPURLResponse {
func find(header: String) -> String? {
let keyValues = allHeaderFields.map { (String(describing: [=10=].key).lowercased(), String(describing: [=10=].value)) }
if let headerValue = keyValues.filter({ [=10=].0 == header.lowercased() }).first {
return headerValue.1
}
return nil
}
}
这是我的。我没有弄乱字典的工作方式,而是在 NSHTTPURLResponse
上创建了一个 obj-c 类别。 Obj-C allHeaderFields
字典仍然不区分大小写。
@import Foundation;
@implementation NSHTTPURLResponse (CaseInsensitive)
- (nullable NSString *)allHeaderFieldsValueForCaseInsensitiveKey:(nonnull NSString *)key {
NSString *value = self.allHeaderFields[key];
if ([value isKindOfClass:[NSString class]]) {
return value;
} else {
return nil;
}
}
@end
简单案例使用
if let ix = headers.index(where: {[=10=].key.caseInsensitiveCompare("eTag") == .orderedSame}){
let tag = headers[ix].value
}
在 swift 4.1
它对我有用。
if let headers = httpResponse.allHeaderFields as? [String: String], let value = headers["key"] {
print("value: \(value)")
}
如果不能像下面那样工作,您也可以使用 .lowercased()
和 .capitalized
值(根据您的情况)
httpResponse.allHeaderFields["eTag".capitalized] as? String
httpResponse.allHeaderFields["eTag". lowercased()] as? String
更有效的解决方法:
(response.allHeaderFields as NSDictionary)["etag"]
由于 bug in Swift and a new solution in iOS 13,我进行了扩展:
这里是link to gist.
public extension HTTPURLResponse {
func valueForHeaderField(_ headerField: String) -> String? {
if #available(iOS 13.0, *) {
return value(forHTTPHeaderField: headerField)
} else {
return (allHeaderFields as NSDictionary)[headerField] as? String
}
}
}
转换为 Swift 3 我注意到从 HTTPURLResponse 读取 header 字段时出现了一个奇怪的错误。
let id = httpResponse.allHeaderFields["eTag"] as? String
不再有效。
我打印了所有 header 的字典,我所有的 header 键似乎都是句子大小写。
根据 Charles 代理,我所有的 header 都是小写的。根据后端团队的说法,在他们的代码中 header 位于 Title-Case 中。根据文档:headers 应该是 case-insensitive。
所以我不知道该相信哪个。是否还有其他人在 Swift 3 中发现他们的 header 现在被 iOS 变成了 Sentence case?如果是这样,这是我们想要的行为吗?
我应该用 Apple 记录一个错误,还是应该在 HTTPURLResponse 上创建一个类别,让我自己不区分大小写地找到一个 header 值。
更新:这是一个known issue.
allHeaderFields
应该 return 一个不区分大小写的字典,因为这是 HTTP 规范所要求的。看起来像 Swift 错误,我会在 .
下面是一些简单重现问题的示例代码:
let headerFields = ["ETag" : "12345678"]
let url = URL(string: "http://www.example.com")!
let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: headerFields)!
response.allHeaderFields["eTaG"] // nil (incorrect)
headerFields["eTaG"] // nil (correct)
(改编自 this Gist from Cédric Luthi。)
作为 Swift 3 的修补程序,您可以这样做:
// Converting to an array of tuples of type (String, String)
// The key is lowercased()
let keyValues = response.allHeaderFields.map { (String(describing: [=10=].key).lowercased(), String(describing: [=10=].value)) }
// Now filter the array, searching for your header-key, also lowercased
if let myHeaderValue = keyValues.filter({ [=10=].0 == "X-MyHeaderKey".lowercased() }).first {
print(myHeaderValue.1)
}
更新:此问题似乎仍未在 Swift 4.
中修复我点击了这个并使用 Dictionary
上的扩展来解决它来创建自定义下标。
extension Dictionary {
subscript(key: String) -> Value? {
get {
let anyKey = key as! Key
if let value = self[anyKey] {
return value // 1213ns
}
if let value = self[key.lowercased() as! Key] {
return value // 2511ns
}
if let value = self[key.capitalized as! Key] {
return value // 8928ns
}
for (storedKey, storedValue) in self {
if let stringKey = storedKey as? String {
if stringKey.caseInsensitiveCompare(key) == .orderedSame {
return storedValue // 22317ns
}
}
}
return nil
}
set {
self[key] = newValue
}
}
}
评论中的时间来自不同场景的基准测试(优化构建,-Os
,平均超过 1,000,000 次迭代)。标准字典的等效访问在 1257ns 时出现。必须进行两次检查有效地使它翻了一番,2412ns。
在我的特殊情况下,我看到 header 从 camel-case 或 lower-case 的服务器返回,具体取决于我连接的网络(其他去弄清楚)。这样做的好处是,如果它得到修复,我可以删除扩展而无需更改其他任何内容。此外,使用该代码的其他任何人都不需要记住任何解决方法 - 他们可以免费获得这个。
我检查了一下,没有看到 ETag
被 HTTPURLResponse
修改了 - 如果我通过了它 ETag
,或者 Etag
我在 [=18= 中得到了那些].如果性能是一个问题,并且您遇到了这个问题,您可以创建第二个下标,它采用包含数组的 Hashable
结构。然后将其传递给 Dictionary,其中包含您要处理的标签。
struct DictionaryKey: Hashable {
let keys: [String]
var hashValue: Int { return 0 } // Don't care what is returned, not going to use it
}
func ==(lhs: DictionaryKey, rhs: DictionaryKey) -> Bool {
return lhs.keys == rhs.keys // Just filling expectations
}
extension Dictionary {
subscript(key: DictionaryKey) -> Value? {
get {
for string in key.keys {
if let value = self[string as! Key] {
return value
}
}
return nil
}
}
}
print("\(allHeaderFields[DictionaryKey(keys: ["ETag", "Etag"])])"
如您所料,这几乎等同于进行单独的字典查找。
有一个 little-bit 比 Darko 的 swift 3.0 版本更短的版本。
由于 header 名称在 iOS8 和 iOS10 中的大小写可能不同,因此最好的方法是使用不区分大小写的比较。
response.allHeaderFields.keys.contains(where: {[=10=].description.caseInsensitiveCompare("CaSe-InSeNsItIvE-HeAdEr") == .orderedSame})
所以现在支持所有类型:
- case-insensitive-header
- Case-Insensitive-Header
- CASE-INSENSITIVE-HEADER
根据@Darko 的回答,我做了一个 Swift 3
扩展,可以让您找到不区分大小写的任何 headers:
import Foundation
extension HTTPURLResponse {
func find(header: String) -> String? {
let keyValues = allHeaderFields.map { (String(describing: [=10=].key).lowercased(), String(describing: [=10=].value)) }
if let headerValue = keyValues.filter({ [=10=].0 == header.lowercased() }).first {
return headerValue.1
}
return nil
}
}
这是我的。我没有弄乱字典的工作方式,而是在 NSHTTPURLResponse
上创建了一个 obj-c 类别。 Obj-C allHeaderFields
字典仍然不区分大小写。
@import Foundation;
@implementation NSHTTPURLResponse (CaseInsensitive)
- (nullable NSString *)allHeaderFieldsValueForCaseInsensitiveKey:(nonnull NSString *)key {
NSString *value = self.allHeaderFields[key];
if ([value isKindOfClass:[NSString class]]) {
return value;
} else {
return nil;
}
}
@end
简单案例使用
if let ix = headers.index(where: {[=10=].key.caseInsensitiveCompare("eTag") == .orderedSame}){
let tag = headers[ix].value
}
在 swift 4.1
它对我有用。
if let headers = httpResponse.allHeaderFields as? [String: String], let value = headers["key"] {
print("value: \(value)")
}
如果不能像下面那样工作,您也可以使用 .lowercased()
和 .capitalized
值(根据您的情况)
httpResponse.allHeaderFields["eTag".capitalized] as? String
httpResponse.allHeaderFields["eTag". lowercased()] as? String
更有效的解决方法:
(response.allHeaderFields as NSDictionary)["etag"]
由于 bug in Swift and a new solution in iOS 13,我进行了扩展:
这里是link to gist.
public extension HTTPURLResponse {
func valueForHeaderField(_ headerField: String) -> String? {
if #available(iOS 13.0, *) {
return value(forHTTPHeaderField: headerField)
} else {
return (allHeaderFields as NSDictionary)[headerField] as? String
}
}
}