Создайте словарь Swift, где ключ "Тип"?

Я пытаюсь сделать такое.

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

но - я не могу:)

введите описание изображения здесь

Необъявленный тип 'Тип'

В этом примере CellThing - мой базовый класс, поэтому A:CellThing, B:CellThing, C:CellThing и так далее. Идея в том, что я буду хранить различные A A, B B, C C C C в массивах словарей.

Как создать "Тип" (в идеале, я полагаю, ограниченным для CellThing) будет ключом в словаре Swift?

Я ценю, что могу (возможно?) использовать String(describing: T.self), но это заставит меня потерять сон.


Здесь пример использования, предполагаемый код будет выглядеть примерно так...

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

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

Итак, что-то вроде...

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
  }

Ответ 1

К сожалению, в настоящее время невозможно, чтобы типы метатипов соответствовали протоколам (см. Этот связанный вопрос по данному вопросу) - поэтому CellThing.Type настоящее время не соответствует и не может соответствовать Hashable. Следовательно, это означает, что его нельзя использовать непосредственно как Key Dictionary.

Однако вы можете создать оболочку для метатипа, используя ObjectIdentifier, чтобы обеспечить реализацию Hashable. Например:

/// 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 }
}

Затем вы также можете предоставить HashableType для Dictionary который принимает метатип и оборачивает его в HashableType для вас:

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

который затем мог бы использовать так:

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]

Это также должно хорошо работать для обобщений, вместо этого вы просто T.self бы словарь в T.self.


К сожалению, одним из недостатков использования нижнего индекса с get и set является то, что вы столкнетесь с падением производительности при работе со значениями словаря, которые являются типами копирования при записи, такими как Array (например, в вашем примере). Я говорю об этой проблеме больше в этом Q & A.

Простая операция, как:

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

вызовет O (N) копию массива, хранящегося в словаре.

Эта проблема предназначена для решения с помощью обобщенных средств доступа, которые были реализованы как неофициальная языковая функция в Swift 5. Если вам удобнее использовать неофициальную языковую функцию, которая может сломаться в будущей версии (не рекомендуется для производственного кода), тогда вы могли бы реализовать индекс как:

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

Ответ 2

Если вы расширяете тип словаря, вы можете напрямую использовать уже определенный универсальный Key.

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

Это работает, потому что в словаре уже есть общие Key и Value определенные для его собственного использования.

Ответ 3

Надежда AnyHashable помогает. Но он появился в Xcode 8.0

Вы можете сделать что-то вроде:

var info: [AnyHashable : Any]? = nil