Make a Swift Dictionary Where the Key Is "Type"

Make a Swift dictionary where the key is Type?

Unfortunately, it's currently not possible for metatype types to conform to protocols (see this related question on the matter) – so CellThing.Type does not, and cannot, currently conform to Hashable. This therefore means that it cannot be used directly as the Key of a Dictionary.

However, you can create a wrapper for a metatype, using ObjectIdentifier in order to provide the Hashable implementation. For example:

/// Hashable wrapper for a metatype value.
struct HashableType<T> : Hashable {

static func == (lhs: HashableType, rhs: HashableType) -> Bool {
return lhs.base == rhs.base
}

let base: T.Type

init(_ base: T.Type) {
self.base = base
}

func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(base))
}
// Pre Swift 4.2:
// var hashValue: Int { return ObjectIdentifier(base).hashValue }
}

You can then also provide a convenience subscript on Dictionary that takes a metatype and wraps it in a HashableType for you:

extension Dictionary {
subscript<T>(key: T.Type) -> Value? where Key == HashableType<T> {
get { return self[HashableType(key)] }
set { self[HashableType(key)] = newValue }
}
}

which could then use like so:

class CellThing {}
class A : CellThing {}
class B : CellThing {}

var recycle: [HashableType<CellThing>: [CellThing]] = [:]

recycle[A.self] = [A(), A(), A()]
recycle[B.self] = [B(), B()]

print(recycle[A.self]!) // [A, A, A]
print(recycle[B.self]!) // [B, B]

This should also work fine for generics, you would simply subscript your dictionary with T.self instead.


Unfortunately one disadvantage of using a subscript with a get and set here is that you'll incur a performance hit when working with dictionary values that are copy-on-write types such as Array (such as in your example). I talk about this issue more in this Q&A.

A simple operation like:

recycle[A.self]?.append(A())

will trigger an O(N) copy of the array stored within the dictionary.

This is a problem that is aimed to be solved with generalised accessors, which have been implemented as an unofficial language feature in Swift 5. If you are comfortable using an unofficial language feature that could break in a future version (not really recommended for production code), then you could implement the subscript as:

extension Dictionary {
subscript<T>(key: T.Type) -> Value? where Key == HashableType<T> {
get { return self[HashableType(key)] }
_modify {
yield &self[HashableType(key)]
}
}
}

which solves the performance problem, allowing an array value to be mutated in-place within the dictionary.

Otherwise, a simple alternative is to not define a custom subscript, and instead just add a convenience computed property on your type to let you use it as a key:

class CellThing {
// Convenience static computed property to get the wrapped metatype value.
static var hashable: HashableType<CellThing> { return HashableType(self) }
}

class A : CellThing {}
class B : CellThing {}

var recycle: [HashableType<CellThing>: [CellThing]] = [:]

recycle[A.hashable] = [A(), A(), A()]
recycle[B.hashable] = [B(), B()]

print(recycle[A.hashable]!) // [A, A, A]
print(recycle[B.hashable]!) // [B, B]

Is it possible to use a Type as a dictionary key in Swift?

Is it possible to use a Type as a dictionary key in Swift?

Well its possible, here is one way:

protocol Growable { ... }

struct S : Growable { ... }
class C : Growable { ... }

extension Dictionary where Key : LosslessStringConvertible
{
subscript(index: Growable.Type) -> Value?
{
get
{
return self[String(describing: index) as! Key]
}
set(newValue)
{
self[String(describing: index) as! Key] = newValue
}
}
}

var d : [String:Int] = [:]
d[S.self] = 42
d[C.self] = 24
print(d)

prints:

["C": 24, "S": 42]

If you change the subscript definition to:

subscript(index: Any.Type) -> Value?

you can of course use any type as a key:

var d : [String:Int] = [:]
d[S.self] = 42
d[C.self] = 24
d[type(of:d)] = 18
print(d)

prints:

["C": 24, "S": 42, "Dictionary<String, Int>": 18]

I'll leave it up to you to decide whether this is wise, but its clearly possible.

[Note: you cannot constrain Key to be String hence the use of the protocol LosslessStringConvertible; there might be a better choice, the Swift standard library is a moving target...]

HTH

Converting Dictionary Key type in Swift 5 with Dictionary(uniqueKeysWithValues:)

You'd better explicitly highlight type like this:

extension KeyedParameters {
var parameters: Parameters {
return Parameters(uniqueKeysWithValues:
self.map { (key, value) in (key.rawValue, value) }
)
}
}

Worked for me.

Extension on dictionary type where key and value have specific types

You can constrain your Dictionary extension to only work apply to keys and values that are StringLiteralConvertable by declaring it like this:

extension Dictionary where Key: StringLiteralConvertible, Value: StringLiteralConvertible {
mutating func insert(stringOptinal optional : String?, forStringKey stringKey : String) {
if let stringValue = optional {
self[stringKey as! Key] = stringValue as? Value
}
}
}

Your example will then generate a compiler error, but at least it won't crash.

How to use a custom class type to be the key in Dictionary in Swift?

Any custom type that you want to use a dictionary key must conform with the Hashable protocol.

This protocol has one property that you must implement.

var hashValue: Int { get }

Use this property to generate an int that Dictionary can use for lookup reasons. You should try to make it so the generated hashValue is unique for each pixel.

The Swift book has the following note, so you can probably make a random hash (as long as it's unique):

The value returned by a type's hashValue property is not required to be the same across different executions of the same program, or in different programs.

Note that because Hashable inherits from Equatable you must also implement:

func ==(_ lhs: Self, _ rhs: Self) -> Bool.

I'm not sure what the internal structure of your pixel is, but you could probably consider two pixels equal when both have the same "x" and "y" values. The final logic is up to you.

Modify this as you need:

struct Pixel : Hashable {

// MARK: Hashable
var hashValue: Int {
get {
// Do some operations to generate a unique hash.
}
}
}

//MARK: Equatable
func ==(lh: Pixel, rh: Pixel) -> Bool {
return lh.x == rh.x && rh.y == lh.y
}

Reference as key in swift dictionary

Equality can be implemented as object identity, i.e. a == b iff a and b refer to the same instance of the class, and the hash value can be build from the ObjectIdentifier (which is the same for identical objects, compare e.g. Difference between using ObjectIdentifier() and '===' Operator):

For Swift 4.2 and later:

class Test : Hashable {
static func ==(lhs: Test, rhs: Test) -> Bool {
return lhs === rhs
}

public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}

For Swift 3:

class Test : Hashable {
var hashValue: Int { return ObjectIdentifier(self).hashValue }
}

func ==(lhs: Test, rhs: Test) -> Bool {
return lhs === rhs
}

For Swift 2.3 and earlier, you can use

/// Return an UnsafePointer to the storage used for `object`.  There's
/// not much you can do with this other than use it to identify the
/// object
func unsafeAddressOf(object: AnyObject) -> UnsafePointer<Void>

i.e.

class Test : Hashable {
var hashValue: Int { return unsafeAddressOf(self).hashValue }
}

func ==(lhs: Test, rhs: Test) -> Bool {
return lhs === rhs
}

Example:

var dictionary = [Test: String]()
let a = Test()
let b = Test()
dictionary[a] = "A"
print(dictionary[a]) // Optional("A")
print(dictionary[b]) // nil

implement the Equatable protocol.



Related Topics



Leave a reply



Submit