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.