Reputation: 12592
I'm trying to do this sort of thing ..
static var recycle: [Type: [CellThing]] = []
but - I can't :)
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
}
Upvotes: 28
Views: 12375
Reputation: 21
Since quite some time has passed since Hamish's answer - it's now also possible to use type as a key to a dictionary without constraining your type in any way or using a HashableType
wrapper. It's possible because you can create an ObjectIdentifier
for any metatype and because ObjectIdentifier
is Hashable
.
If you define an extension for Dictionary
:
extension Dictionary where Key == ObjectIdentifier {
subscript<T>(key: T.Type) -> Value? {
get { return self[ObjectIdentifier(T.self)] }
set { self[ObjectIdentifier(T.self)] = newValue }
}
}
Then you define your Dictionary and can use it like this:
var dictionary: [ObjectIdentifier: Any] = [:]
dictionary[A.self] = [A(), A(), A()]
dictionary[B.self] = [B(), B(), B()]
This method works for everything that has a metatype: protocol, struct, class, enum. Here is full example, including how to define a generic protocol conformance constraint on a custom implementation.
extension Dictionary where Key == ObjectIdentifier {
subscript<T>(key: T.Type) -> Value? {
get { return self[ObjectIdentifier(T.self)] }
set { self[ObjectIdentifier(T.self)] = newValue }
}
}
protocol MyProtocol {
}
struct MyStruct {
let data = "struct"
}
class MyClass: MyProtocol {
let data = "class"
}
enum MyEnum: String {
case myCase
}
func example() {
var dictionary: [ObjectIdentifier: Any] = [:]
dictionary[MyProtocol.self] = "MyProtocol"
dictionary[MyProtocol.Protocol.self] = "MyProtocol Protocol"
dictionary[MyStruct.self] = MyStruct()
dictionary[MyClass.self] = MyClass()
dictionary[MyEnum.self] = MyEnum.myCase
print(String(describing: dictionary[MyProtocol.self]!)) // "MyProtocol"
print(String(describing: dictionary[MyProtocol.Protocol.self]!)) // "MyProtocol Protocol"
print(String(describing: dictionary[MyStruct.self]!)) // MyProject.MyStruct(data: "struct")
print(String(describing: dictionary[MyClass.self]!)) // MyProject.MyClass
print(String(describing: dictionary[MyEnum.self]!)) // myCase
}
// It might be useful to constrain what can be put into a dictionary
// based on protocol conformance or others:
protocol Resource {
}
class MyResource: Resource {
let message = "Swift is cool"
}
class ResourceStorageByType {
var dictionary: [ObjectIdentifier: Any] = [:]
func add<T: Resource>(_ item: T) {
dictionary[T.self] = item
}
func get<T: Resource>(_ type: T.Type) -> T? {
return dictionary[T.self] as? T
}
}
func example2() {
let storage = ResourceStorageByType()
storage.add(MyResource())
if let retrievedResource = storage.get(MyResource.self) {
print(retrievedResource.message) // Swift is cool
}
}
Upvotes: 2
Reputation: 80781
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]
Upvotes: 41
Reputation: 1750
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.
Upvotes: 1
Reputation: 1822
Hope AnyHashable helps. But It appeared in Xcode 8.0
You can do something like:
var info: [AnyHashable : Any]? = nil
Upvotes: -5