public
struct
Key : RawRepresentable, Equatable, Hashable
{
public private(set) var rawValue: String
public init(rawValue: String) { self.rawValue = rawValue }
public init(_ rawValue: String) { self.rawValue = rawValue }
}
static let code = Key("code")
And I'd like to use these as keys in a dictionary. But the dictionary comes from Objective-C, and I'm not sure how to handle the type coercion. E.g., I'd like to do something like this:
@objc
foo(dictionary inDict: [String:AnyObject])
{
if let errorCode = inDict[.code]
{
}
}
I want the dictionary key to be of type Key, so that the compiler enforces the use of the symbolic constants. Is there a way to cast [String:AnyObjet] to [Key:AnyObjet]? It can't be the parameter type, because Objective-C can't call it that way (according to the compiler).
extension Dictionary where Key == String {
public subscript(_ key: MyModule.Key) -> Value {
// “MyModule.Key” because “Key” alone clashes with Dictionary’s generic “Key”.
get {
return self[key.rawValue]
}
set {
self[key.rawValue] = newValue
}
}
}
It would allow you to do this:
let dictionary: [String: Any] = [:]
dictionary[.code] = true
if dictionary[.code] == true {
print("Stored and retrieved with a strongly typed “Key”.")
}
If you also want to make it impossible to use the raw strings, you would instead have to map the keys and vend it as [Key: AnyObject]:
public func getDictionary() -> [Key: AnyObject] {
let underlying = getFromObjectiveC()
let pairs = underlying.lazy.map({ (Key($0), $1) })
return Dictionary(pairs, uniquingKeysWith: { first, _ in first })
}
Note that this will be inefficient, especially if you need to repeatedly go back and forth. And It is still impossible to stop indirect string usage like this:
let dictionary = getDictionary()
dictionary[Key(rawValue: "code")] = true
P.S. Unless there are contextual constraints you have not mentioned, this would be a much more concise way to define your Key type:
public enum Key : String {
case code
// raw value is also “code”
case modified = "overridden"
// raw value is “overridden” instead of “modified”.
}
I was hoping to avoid extending Dictionary; I was hoping I could just cast to the desired type. It's okay, I think I'm going to go a different way altogether. The enum might be fine. It just would've been cool to be able to type a period and use code completion to get the available key names.
The NS_TYPED_ENUM approach is the best path assuming you can control the Objective-C side. If not, I think the best approach is to wrap the dictionary in a struct which you can index with a strongly-typed key as a client; the implementation of the struct just calls through to the dictionary with the corresponding string value. This does not incur 'bridging' costs like the map approach as you aren't manipulating the Dictionary storage.
struct CustomStruct {
private let dict: [String : AnyObject]
init(from dict: [String : AnyObject]) {
self.dict = dict
}
subscript (_ key: Key) -> AnyObject? {
return self.dict[key.stringValue]
}
enum Key : String { // this could be a `RawRepresentable` struct if you want it to be
case code
}
}
You could also do fancier stuff in the struct like make generic subscripts or validating the dictionary with a failable initializer.