В проекте мы используем классы для слоя модели, поэтому мне приходится писать такой код:
// MARK: - Hashable
extension Player: Hashable {
static func == (lhs: Player, rhs: Player) -> Bool {
return lhs.hashValue == rhs.hashValue
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.name)
}
}
Можно ли как-то избежать этого шаблона? Можно ли реализовать Equatable
сравнение по .hashValue
по умолчанию? Спасибо.
@EternalBlack В Swift 4.2 использование Sourcery для создания Equatable и Hashable просто не нужно. Кстати, автогенерируемый код ничем не лучше кода, написанного вручную. Шаблон все еще там. Даже ошибки могут быть в сгенерированном коде.
Это неправильно, и было бы бессмысленно, если бы компилятор синтезировал его автоматически:
static func == (lhs: Player, rhs: Player) -> Bool {
return lhs.hashValue == rhs.hashValue
}
Идентичные объекты должны иметь одинаковое хеш-значение, но не наоборот: разные объекты могу имеют одинаковое хеш-значение.
Конкретно, в вашем примере: имя - это строка, и существует бесконечно много разных строк, но только 264 разных хеш-значения. Таким образом, должны быть две разные строки с одинаковым значением хеш-функции.
Если все сохраненные свойства Hashable
, то компилятор может полностью синтезировать соответствие для вас. Например
struct Player : Equatable, Hashable {
let name: String
var score: Int
}
Здесь два игрока являются «идентичными», если у них одинаковое имя и одинаковый счет.
Если есть нехэшируемые свойства или если вы хотите настроить концепцию идентичности, вам необходимо соответствующим образом переопределить ==
а такжеhash(into)
. Хэш-функция должна использовать те же свойства, которые определяют идентичность в ==
. Например
struct Player : Equatable, Hashable {
let name: String
var score: Int
static func == (lhs: Player, rhs: Player) -> Bool {
return lhs.name == rhs.name
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.name)
}
}
Теперь два игрока считаются «идентичными», если у них одинаковое имя.
Извините, но я не понимаю, почему моя реализация Equtable неверна. Не могли бы вы показать пример, когда это может привести к неожиданному поведению?
@BohdanSavych: имя - нить, и существует бесконечно много разных строк, но только 2 ^ 64 разных хэш-значения. Таким образом, должны быть две разные строки с одинаковым значением хеш-функции.
Вы можете написать свой собственный шаблон с помощью языка разметки Stencil и автоматически сгенерировать код, используя библиотеку Sourcery.
Или используйте существующие решения (шаблоны AutoEquatable, AutoHashable Sourcery).
А еще вы могли бы написать что-то вроде этого:
protocol IHash: class { }
extension IHash where Self: Hashable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}
class User: IHash, Hashable {
var name: String = ""
func hash(into hasher: inout Hasher) {
hasher.combine(self.name)
}
}
Это поможет вам избежать дублирования в разных классах.
Спасибо, Андрей, это именно то, что мне было нужно.
Нет, сравнение только значения хеша в ==
равно неправильно.
Вы можете использовать источник с настраиваемым шаблоном и просто расширить свой класс, например, до
autoHashable
.