Previously, we saw how to conform to
Hashable
to use a custom type as a
Key
in a
Dictionary
, but what if we want to use a set of different types as keys in the same dictionary? Let's look at an example.
protocol DataKey : Hashable {
}
class DataController {
var allResults:[DataKey:String] = [:]
}
We have a
DataKey
class, which may have many members not listed here. Perhaps we want to store "results", whatever that is, keyed by these
DataKey
s. So we write a
Dictionary
, but we can't just declare the
Key
type to be
Hashable
. This comes from
Equatable
's definition.
public static func ==(lhs: Self, rhs: Self) -> Bool
Equatable
has to know that the types of the left and right hand side of the
==
are already the same. They do this with the
Self
type in their definition. This is called a "Self constraint", and is a form of "same-type contraint". It means that the type of the argument is the "same-type" as whatever type confoms the protocol. Using this
Equatable
(and thus
Hashable
) protocol alone as a constraint, there's no way to know whether the types are actually the same if
==
were called inside the
Dictionary
, because the exact type information is lost at the function call-site when we passed in something knowing only one conformation.
What we need is another type, which can generically wrap a
Hashable
instance, and then check the dynamic type of both the left and right-hand side of the
==
operator, and skip call the wrapped member's implementation if they aren't the same.
We're not even going to write code to do that, because Swift provides it biult-in with the
AnyHashable
type.
class DataController {
//AnyHashable wraps DataKey's
private var allResults:[AnyHashable:String] = [:]
func operation<Key:DataKey>(key:Key)->String? {
return allResults[AnyHashable(key)]
}
func set<Key:DataKey>(operation:String, key:Key) {
allResults[AnyHashable(key)] = operation
}
}
We'll need to use generic functions to constrain the key types to conform to
DataKey
, and then wrap it in an
AnyHashable
. Our original
allResults
Dictionary
uses
AnyHashable
as its
Key
type, and we make it private to prevent other developers from accidentally trying to access it with something that's not a DataKey.
struct ProfileKey : DataKey {
var username:String
static func ==(lhs:ProfileKey, rhs:ProfileKey)->Bool {
return lhs.username == rhs.username
}
var hashValue: Int {
return username.hashValue
}
}
struct PostKey : DataKey {
var postID:Int
static func ==(lhs:PostKey, rhs:PostKey)->Bool {
return lhs.postID == rhs.postID
}
var hashValue: Int {
return postID.hashValue
}
}
let controller = DataController()
So here are two different types, which both conform to
DataKey
.
let profile1Key = ProfileKey(username: "benspratling")
controller.set(result: "Ben Spratling", key: profile1Key)
let sameProfileKey = ProfileKey(username: "benspratling")
controller.result(key: sameProfileKey) //"Ben Spratling"
let profileKeyForSomeoneElse = ProfileKey(username: "adamdill")
controller.result(key: profileKeyForSomeoneElse) //nil
let postKey0 = PostKey(postID: 0123)
controller.set(result: "Introducing Swift 3", key: postKey0)
controller.result(key: PostKey(postID: 0123)) //"Introducing Swift 3"
And now, we're able to store and retrieve values in the same
Dictionary
using these different structs as keys.
Comments for Working around Hashable with AnyHashable