NSObject является Hashable, а протокол, который принимает NSObject, - нет?

Просто посоветуйтесь с сообществом, прежде чем я подам радар:

В файле .h Obj-C:

@protocol myProto <NSObject> 
@end

В файле .swift (который имеет доступ к приведенному выше определению протокола через заголовок моста):

class myClass {
    // This line compiles fine
    var dictOne: [NSObject:Int]?
    // This line fails with "Type 'myProto' does not conform to protocol 'Hashable'"
    var dictTwo: [myProto:Int]?
}

Проверка класса NSObject показывает, что он (или NSObjectProtocol, которому он сопоставляется) не реализует метод hashValue, требуемый протоколом Hashable, и не принимает его явно.

Итак, где-то за кулисами NSObject, несмотря на это, помечается как Hashable, но не распространяется на протоколы, которые принимают NSObject / NSObjectProtocol.

Есть ли у меня ошибка или я что-то упускаю?

:) Тео

Дополнительная информация:

документация предполагает, что:

  • Единственное требование к ключевому типу словаря - это то, что он Hashable и реализует ==.
  • Вы действительно можете использовать протокол.
Hash Values for Dictionary Key Types

Тип должен быть хешируемым, чтобы его можно было использовать в качестве ключевого типа словаря, то есть тип должен обеспечивать способ вычисления хеш-значения для себя. Хеш-значение - это значение типа Int, которое одинаково для всех сравниваемых объектов, так что если a == b, следует, что a.hashValue == b.hashValue.

Все основные типы Swift (такие как String, Int, Double и Bool) по умолчанию хешируются, и все эти типы могут использоваться в качестве ключей словаря. Значения членов перечисления без связанных значений (как описано в разделе «Перечисления») также по умолчанию хешируются.

ПРИМЕЧАНИЕ. Вы можете использовать свои собственные пользовательские типы в качестве типов ключей словаря, сделав их соответствующими протоколу Hashable из стандартной библиотеки Swift. Типы, соответствующие протоколу Hashable, должны предоставлять свойство gettable Int с именем hashValue, а также должны обеспечивать реализацию оператора «равно» (==). Значение, возвращаемое свойством типа hashValue, не обязательно должно быть одинаковым при разных выполнениях одной и той же программы или в разных программах. Для получения дополнительной информации о соответствии протоколам см. Протоколы.


person Teo Sartori    schedule 24.07.2014    source источник


Ответы (3)


NSObjectProtocol не наследуется от Hashable. Это главная проблема.

На самом деле он не может наследовать от Hashable, потому что Hashable требует метода с именем hashValue, а NSObjectProtocol требует метода с именем hash.

С другой стороны, класс NSObject может реализовывать как NSObjectProtocol, так и Hashable.

Та же проблема возникает с Equatable.

Изменить:

Есть еще одна более тонкая проблема. Вы не можете использовать протокол там, где ожидается Equatable, вам всегда нужно использовать тип класса или тип значения, который принимает Equatable. Причина в том, что для ключа недостаточно принять Equatable, все ключи в словаре должны быть приравнены друг к другу.

Например, если у вас есть класс A и класс B, оба соответствуют Equatable, тогда вы можете сравнивать экземпляры A с другими экземплярами A, и вы можете сравнивать экземпляры B с другими экземплярами B, но вы не можете сравнивать экземпляры A с экземплярами B. Вот почему вы не можете использовать экземпляры A и экземпляры B в качестве ключей в одном словаре.

Обратите внимание, что каждый NSObject приравнивается к любому другому NSObject, поэтому NSObject является допустимым типом для ключей в словаре.

person Sulthan    schedule 24.07.2014
comment
Спасибо за ваши комментарии. Я действительно вижу (как упоминалось в моем вопросе), что NSObjectProtocol не реализует требуемый метод Hashable, но я не следую более тонкому аргументу проблемы; Часть Equatable просто сравнивает хэши и не заботится о том, что такое A и B, если они принимают протокол. - person Teo Sartori; 25.07.2014
comment
Проблема, как я вижу, в том, что мост не может сопоставить Swift Hashable hashValue и Equatable == с протоколами Objective-C NSObject isEqual: и hash. - person Teo Sartori; 25.07.2014
comment
@TeoSartori Невозможно соединить их таким образом. Obj-C не может представлять оператор ==, и вы не можете просто связать имя метода с другим именем метода. Объект может реализовать и то, и другое, но вы не можете просто уравнять их, это было бы опасным прецедентом. В любом случае, вы не можете использовать типы протоколов для ключей словаря, даже если это чистый протокол Swift. - person Sulthan; 25.07.2014

Я согласен, что это похоже на недостающую функцию. Для всех, кому интересно, я сделал небольшую обертку, чтобы разобраться с этим.

struct HashableNSObject<T: NSObjectProtocol>: Hashable {
    let value: T

    init(_ value: T) {
        self.value = value
    }

    static func == (lhs: HashableNSObject<T>, rhs: HashableNSObject<T>) -> Bool {
        return lhs.value.isEqual(rhs.value)
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(value.hash)
    }
}

Вы даже можете сделать это не универсальным, заменив T на NSObjectProtocol, но я думаю, что так будет чище.

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

let foo = [MyObjectProtocol]()
let bar = Set<HashableNSObject<MyObjectProtocol>>()
fun1(foo.map { HashableNSObject($0) })
fun2(bar.map { $0.value })
person Guy Kogus    schedule 19.04.2019

В качестве альтернативы вы можете использовать NSObjectProtocol.hash в качестве ключа.

var dictTwo: [Int:Int]?
dictTwo[myProtoInstance.hash] = 0
person Brody Robertson    schedule 02.07.2020
comment
Хэш объекта - это не уникальное значение. Вы можете просто использовать его как ключ в словаре. - person Andriy; 23.03.2021