I hit this and worked around it using an extension on Dictionary
to create custom subscripts.
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
}
}
}
The timings in the comments are from benchmarking different scenarios (optimised build, -Os
, averaged over 1,000,000 iterations). An equivalent access of a standard dictionary, came out at 1257ns. Having to make two checks effectively doubled that, 2412ns.
In my particular case, I was seeing a header come back from the server that was camel-case, or lower-case, depending on the network I was connecting on (something else to investigate). The advantage of this is that, should it get fixed, I can just delete the extension and nothing else needs to change. Also, anyone else using the code doesn't need to remember any workarounds - they get this one for free.
I checked and did not see ETag
being modified by HTTPURLResponse
- if I passed it ETag
, or Etag
I got those back in allHeaderFields
. In the case that performance is a concern, and you are encountering this issue, you can create a second subscript which takes a Hashable
struct containing an array. Then pass it to the Dictionary, with the tags you want to handle.
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"])])"
This is, as you'd expect, almost equivalent to making individual dictionary lookups.