Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make a Swift dictionary where the key is "Type"?

I'm trying to do this sort of thing ..

static var recycle: [Type: [CellThing]] = []

but - I can't :)

enter image description here

Undeclared type 'Type'

In the example, CellThing is my base class, so A:CellThing, B:CellThing, C:CellThing and so on. The idea is I would store various A A A, B B, C C C C in the dictionary arrays.

How to make a "Type" (ideally I guess, constrained to CellThing) be the key in a Swift dictionary?

I appreciate I could (perhaps?) use String(describing: T.self), but that would make me lose sleep.


Here's a use case, envisaged code would look something like this ...

@discardableResult class func make(...)->Self {
  return makeHelper(...)
  }

private class func makeHelper<T: CellThing>(...)->T {
  let c = instantiateViewController(...) as! T
  return c
  }

So then something like ...

static var recycle: [Type: [CellThing]] = []

private class func makeHelper<T: CellThing>(...)->T {
  let c = instantiateViewController(...) as! T

  let t = type whatever of c (so, maybe "A" or "B")
  recycle[t].append( c )

  let k = recycle[t].count
  print wow, you have k of those already!

  return c
  }
like image 395
Fattie Avatar asked Feb 25 '17 17:02

Fattie


People also ask

Is dictionary value type in Swift?

In Swift, Array, String, and Dictionary are all value types. They behave much like a simple int value in C, acting as a unique instance of that data. You don't need to do anything special — such as making an explicit copy — to prevent other code from modifying that data behind your back.

Which one creates a dictionary with a key type of integer?

Swift 4 allows you to create Dictionary from arrays (Key-Value Pairs.)

Which type is defined for key in dictionary in Swift?

The Key type of the dictionary is Int , and the Value type of the dictionary is String . To create a dictionary with no key-value pairs, use an empty dictionary literal ( [:] ).


2 Answers

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]
like image 96
Hamish Avatar answered Oct 21 '22 09:10

Hamish


If you extend the Dictionary type you can use the already defined generic Key directly.

extension Dictionary {
    // Key and Value are already defined by type dictionary, so it's available here
    func getSomething(key: Key) -> Value {
        return self[key]
    }
}

This works because Dictionary already has generics Key and Value defined for it's own use.

like image 38
sevensevens Avatar answered Oct 21 '22 09:10

sevensevens