11

在转换为 Swift 3 时,我注意到从 HTTPURLResponse 读取标头字段时出现了一个奇怪的错误。

let id = httpResponse.allHeaderFields["eTag"] as? String

不再工作。

我打印出所有标题字典,我所有的标题键似乎都在句子的情况下。

根据查尔斯代理,我所有的标题都是小写的。根据后端团队的说法,在他们的代码中,标题是 Title-Case。根据文档:标题应该不区分大小写。

所以我不知道该相信哪个。有没有其他人在 Swift 3 中发现他们的标题现在被 iOS 变成了句子大小写?如果是这样,这是我们想要的行为吗?

我应该用 Apple 记录一个错误,还是应该只在 HTTPURLResponse 上创建一个类别,以允许自己不区分大小写地查找标头值。

4

10 回答 10

11

更新:这是一个已知问题


allHeaderFields应该返回一个不区分大小写的字典,因为这是 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)

(改编自Cédric Luthi 的 Gist。)

于 2016-10-20T11:06:20.240 回答
5

更有效的解决方法:

(response.allHeaderFields as NSDictionary)["etag"]
于 2019-02-11T23:55:24.193 回答
5

根据@Darko 的回答,我做了一个Swift 3扩展,允许您查找任何不区分大小写的标题:

import Foundation


extension HTTPURLResponse {

    func find(header: String) -> String? {
        let keyValues = allHeaderFields.map { (String(describing: $0.key).lowercased(), String(describing: $0.value)) }

        if let headerValue = keyValues.filter({ $0.0 == header.lowercased() }).first {
            return headerValue.1
        }
        return nil
    }

}
于 2017-06-24T22:55:58.707 回答
1

我点击了这个并使用扩展来解决它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。

在我的特定情况下,我看到从服务器返回的标头是驼峰式或小写的,具体取决于我连接的网络(还有其他要调查的内容)。这样做的好处是,如果它得到修复,我可以删除扩展而无需更改任何其他内容。此外,使用该代码的任何其他人都不需要记住任何解决方法——他们免费获得这个。

我检查并没有看到ETag被修改HTTPURLResponse- 如果我通过了它ETag,或者Etag我把它们拿回来了allHeaderFields。如果性能是一个问题,并且您遇到此问题,您可以创建第二个下标,该下标采用Hashable包含数组的结构。然后将其传递给字典,并带有您要处理的标签。

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"])])"

正如您所料,这几乎等同于进行单独的字典查找。

于 2017-02-02T01:20:55.613 回答
1

作为 Swift 3 的热修复,您可以这样做:

// Converting to an array of tuples of type (String, String)
// The key is lowercased()
let keyValues = response.allHeaderFields.map { (String(describing: $0.key).lowercased(), String(describing: $0.value)) } 

// Now filter the array, searching for your header-key, also lowercased
if let myHeaderValue = keyValues.filter({ $0.0 == "X-MyHeaderKey".lowercased() }).first {
    print(myHeaderValue.1)
}

更新:这个问题似乎仍然没有在 Swift 4 中得到解决。

于 2017-01-02T15:08:39.630 回答
1

由于Swift 中的错误和iOS 13 中的新解决方案,我做了一个扩展:

这是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
        }
    }
}

于 2019-11-18T13:58:46.717 回答
0

对于简单的案例使用

if let ix = headers.index(where: {$0.key.caseInsensitiveCompare("eTag") == .orderedSame}){
    let tag = headers[ix].value
}
于 2017-09-19T09:08:59.130 回答
0

这是我的。我没有搞乱字典的工作方式,而是在NSHTTPURLResponse. Obj-CallHeaderFields字典仍然不区分大小写。

@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
于 2017-06-29T19:58:07.290 回答
0

swift 3.0 的版本比 Darko 的版本短一点。

由于 iOS8 和 iOS10 中标题名称大小写可能不同,因此最好的方法是使用不区分大小写的比较。

response.allHeaderFields.keys.contains(where: {$0.description.caseInsensitiveCompare("CaSe-InSeNsItIvE-HeAdEr") == .orderedSame})

所以现在支持所有类型:

  • 不区分大小写的标题
  • 不区分大小写的标题
  • 不区分大小写的标题
于 2017-05-17T11:59:33.003 回答
-1

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
于 2018-11-13T13:50:31.533 回答