Ответ 1
Обновление Swift 3
Теперь вы можете использовать AnyHashable
, который является стираемым хешируемым значением, созданным именно для таких сценариев:
var dict = Dictionary<AnyHashable, Int>()
Я хочу создать Dictionary
, который не ограничивает тип ключа (например, NSDictionary
)
Итак, я попробовал
var dict = Dictionary<Any, Int>()
и
var dict = Dictionary<AnyObject, Int>()
в результате
error: type 'Any' does not conform to protocol 'Hashable'
var dict = Dictionary<Any, Int>()
^
<REPL>:5:12: error: cannot convert the expression type '<<error type>>' to type '$T1'
var dict = Dictionary<Any, Int>()
^~~~~~~~~~~~~~~~~~~~~~
ОК, я буду использовать Hashable
var dict = Dictionary<Hashable, Int>()
но
error: type 'Hashable' does not conform to protocol 'Equatable'
var dict = Dictionary<Hashable, Int>()
^
Swift.Equatable:2:8: note: '==' requirement refers to 'Self' type
func ==(lhs: Self, rhs: Self) -> Bool
^
Swift.Hashable:1:10: note: type 'Hashable' does not conform to inherited protocol 'Equatable.Protocol'
protocol Hashable : Equatable
^
<REPL>:5:12: error: cannot convert the expression type '<<error type>>' to type '$T1'
var dict = Dictionary<Hashable, Int>()
^~~~~~~~~~~~~~~~~~~~~~~~~~~
Итак Hashable
унаследовано от Equatable
, но оно не соответствует Equatable
??? Я не понимаю...
В любом случае, продолжайте пробовать
typealias KeyType = protocol<Hashable, Equatable> // KeyType is both Hashable and Equatable
var dict = Dictionary<KeyType, Int>() // now you happy?
не повезло
error: type 'KeyType' does not conform to protocol 'Equatable'
var dict = Dictionary<KeyType, Int>()
^
Swift.Equatable:2:8: note: '==' requirement refers to 'Self' type
func ==(lhs: Self, rhs: Self) -> Bool
^
Swift.Hashable:1:10: note: type 'KeyType' does not conform to inherited protocol 'Equatable.Protocol'
protocol Hashable : Equatable
^
<REPL>:6:12: error: cannot convert the expression type '<<error type>>' to type '$T1'
var dict = Dictionary<KeyType, Int>()
^~~~~~~~~~~~~~~~~~~~~~~~~~
Я так потерялся, как я могу сделать компилятор счастливым с моим кодом?
Я хочу использовать словарь, например
var dict = Dictionary<Any, Int>()
dict[1] = 2
dict["key"] = 3
dict[SomeEnum.SomeValue] = 4
Я знаю, что могу использовать Dictionary<NSObject, Int>
, но это не совсем то, что я хочу.
Теперь вы можете использовать AnyHashable
, который является стираемым хешируемым значением, созданным именно для таких сценариев:
var dict = Dictionary<AnyHashable, Int>()
Я считаю, что с Swift 1.2 вы можете использовать для этого структуру ObjectIdentifier. Он реализует Hashable (и, следовательно, Equatable), а также Comparable. Вы можете использовать его для переноса любого экземпляра класса. Я предполагаю, что реализация использует обернутый объект, лежащий в основе адреса для hashValue, а также внутри оператора ==.
Я взял на себя смелость перекрестной публикации/ссылки на этот вопрос на отдельном посте на форумах Apple Dev, и этот вопрос ответил здесь,
Этот ответ из приведенной выше ссылки работает в 6.1 и выше:
struct AnyKey: Hashable {
private let underlying: Any
private let hashValueFunc: () -> Int
private let equalityFunc: (Any) -> Bool
init<T: Hashable>(_ key: T) {
underlying = key
// Capture the key hashability and equatability using closures.
// The Key shares the hash of the underlying value.
hashValueFunc = { key.hashValue }
// The Key is equal to a Key of the same underlying type,
// whose underlying value is "==" to ours.
equalityFunc = {
if let other = $0 as? T {
return key == other
}
return false
}
}
var hashValue: Int { return hashValueFunc() }
}
func ==(x: AnyKey, y: AnyKey) -> Bool {
return x.equalityFunc(y.underlying)
}
Словарь struct Dictionary<Key : Hashable, Value>...
Это означает, что Value может быть любым, что вам нужно, и Key может быть любым типом, который вам нужен, но Key должен соответствовать протоколу Hashable
.
Вы не можете создать Dictionary<Any, Int>()
или Dictionary<AnyObject, Int>()
, потому что Any
и AnyObject
не могут гарантировать, что такой ключ соответствует Hashable
Вы не можете создать Dictionary<Hashable, Int>()
, потому что Hashable
не тип, это просто протокол, который описывает необходимый тип.
Итак, Hashable унаследован от Equatable, но он не соответствует Equatable??? Я не понимаю...
Но вы ошибаетесь в терминологии. Исходная ошибка
type 'Hashable' does not conform to inherited protocol 'Equatable.Protocol'
Это означает, что Xcode предполагает "Hashable" как некоторый тип, но такого типа нет. И Xcode рассматривает его как некий пустой тип, который явно не соответствует ни одному протоколу (в этом случае он не соответствует унаследованному протоколу Equatable
)
Что-то подобное происходит с KeyType
.
Объявление псевдонима типа вводит именованный псевдоним существующего типа в вашу программу.
Вы видите existing type
. protocol<Hashable, Equatable>
не является типом протокола, поэтому Xcode снова сообщает вам, что type 'KeyType' does not conform to protocol 'Equatable'
Вы можете просто использовать Dictionary<NSObject, Int>
, потому что NSObject
соответствует протоколу Hashable
.
Swift - сильный язык ввода, и вы не можете делать такие вещи, как creating Dictionary that can hold anything in Key
. На самом деле словарь уже поддерживает любое может содержать что-либо в Key, которое соответствует Hashable
. Но так как вы должны указать конкретный класс, вы не можете сделать это для собственных классов Swift, потому что в Swift нет такого мастер-класса, как в Objective-C, который соответствует воздуху, может соответствовать (с помощью расширений) до Hashable
Конечно, вы можете использовать некоторую оболочку, такую как chrisco
. Но я действительно не могу представить, зачем вам это нужно. Замечательно, что у вас есть сильная печать в Swift, поэтому вам не нужно беспокоиться о типе кастинга, как вы это делали в Objective-C
Hashable
- это просто протокол, поэтому вы не можете указывать его напрямую как тип для значения Key
. То, что вам действительно нужно, это способ выражения "любого типа T, так что T реализует Hashable
. Это обрабатывается ограничениями типа в Swift:
func makeDict<T: Hashable>(arr: T[]) {
let x = Dictionary<T, Int>()
}
Этот код компилируется.
AFAIK, вы можете использовать ограничения типов только для общих функций и классов.
Это точно не отвечает на вопрос, но помогло мне.
Общий ответ - реализовать Hashable для всех ваших типов, однако для протоколов может быть сложно, поскольку Hashable расширяет Equatable и Equatable uses Self, который налагает серьезные ограничения на то, для чего может использоваться протокол.
Вместо этого выполните Printable, а затем выполните:
var dict = [String: Int]
dict[key.description] = 3
Реализация описания должна быть примерно такой:
var description : String {
return "<TypeName>[\(<Field1>), \(<Field2>), ...]"
}
Не лучший ответ, но лучшее, что у меня есть до сих пор: (
Это не отвечает на вопрос ОП, но имеет несколько взаимосвязь и, возможно, может быть полезным для некоторых ситуаций. Предположим, что вы действительно хотите это сделать:
public var classTypeToClassNumber = [Any.Type : Int]()
Но Swift говорит вам: "Тип" Any.Type "не соответствует протоколу Hashable".
Большинство приведенных выше ответов касаются использования экземпляров объектов в качестве словарного ключа, а не типа объекта. (Что справедливо, о том, о чем спрашивал ОП.) Это был ответ Говарда Ловата, который привел меня к полезному решению.
public class ClassNumberVsClassType {
public var classTypeToClassNumber = [String : Int]()
public init() {
classTypeToClassNumber[String(describing: ClassWithStringKey.self)] = 367622
classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList3.self)] = 367629
classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList2.self)] = 367626
classTypeToClassNumber[String(describing: ClassWithGuidKey.self)] = 367623
classTypeToClassNumber[String(describing: SimpleStruct.self)] = 367619
classTypeToClassNumber[String(describing: TestData.self)] = 367627
classTypeToClassNumber[String(describing: ETestEnum.self)] = 367617
classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList0.self)] = 367624
classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList1.self)] = 367625
classTypeToClassNumber[String(describing: SimpleClass.self)] = 367620
classTypeToClassNumber[String(describing: DerivedClass.self)] = 367621
}
public func findClassNumber(_ theType : Any.Type) -> Int {
var s = String(describing: theType)
if s.hasSuffix(".Type") {
s = s.substring(to: s.index(s.endIndex, offsetBy: -5)) // Remove ".Type"
}
let classNumber = _classTypeToClassNumber[s]
return classNumber != nil ? classNumber! : -1
}
}
EDIT:
Если задействованные классы определены в разных модулях и могут иметь конфликтующие имена классов, если вы пренебрегаете именем модуля, тогда замените "String (reflecting:" для "String (описывая:" ), как при создании словаря, так и когда выполняя поиск.
Вы можете использовать имя класса как Hashable, например:
var dict = [String: Int]
dict[object_getClassName("key")] = 3
См. Как напечатать тип или класс переменной в Swift? для получения имени класса.