Приравниваемые, сопоставимые, идентифицируемые и хэшируемые решения
Протоколы, безусловно, не новы для iOS или ее родственной OS X; действительно, протокол делегата — это хлеб с маслом более чем для половины фреймворков, хотя с появлением нового async/await он может измениться в ближайшие годы. Тем не менее, протоколы, похоже, тем временем меняют свои цвета с тех пор, как SwiftUI вышел в 2019 году.
Я говорю это потому, что SwiftUI включает в себя несколько обязательных протоколов, связанных с самим языком. Базовые протоколы, такие как Equatable, Comparable, Identifying и Hashable, хотя не всегда очевидно, что происходит.
Идентифицируемый
Это первый протокол в качестве нового кодера SwiftUI, с которым вы, вероятно, столкнетесь, когда попытаетесь определить цикл ForEach, например, внутри List — при условии, что у нас есть массив кубиков, содержащий пользовательскую структуру.
struct ContentView: View {
@State var dice = [Dice]()
var body: some View {
ForEach(dice) {
Text(String($0.value))
}
}
}
Компилятору нужно средство уникальной идентификации каждой строки в цикле вашей структуры. Показанная здесь переменная dice должна соответствовать Identifiable. Соответствие, которое вы получаете с таким кодом.
struct Dice: Identifiable {
// var id = UUID()
var id = Date().timeIntervalSince1970 // epoch [dies Jan 19, 2038]
var value: Int!
}
Хэшируемый
Второй протокол, с которым вы, вероятно, столкнетесь, — это Hashable, который SwiftUI потребуется для цикла, подобного показанному здесь.
ForEach(dice, id: \.self) { die in
Text("Die: \(die.value)")
}
Но будьте осторожны, потому что если вы впоследствии добавите третий протокол Equatable с показанным определением — это приведет к сбою вашего кода.
struct Dice: Equatable, Hashable {
var id = UUID()
var value: Int!
static func ==(lhs: Dice, rhs: Dice) -> Bool {
lhs.value == rhs.value
}
}
Требование хешируемости здесь нуждается в уникальном идентификаторе, таком как идентифицируемый протокол (почему он запрашивает Hashable, когда он нуждается в идентифицируемом, за пределами моего понимания).
Чтобы использовать как протокол Hashable, так и протокол Equatable, вам нужно сообщить протоколу Hashable, чтобы он сосредоточился на идентификаторе, поэтому да, это уникальное значение Identifiable.
extension Dice: Hashable {
static func ==(lhs: Dice, rhs: Dice) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
Тем не менее, хэш-функция может пригодиться и здесь, поскольку она гарантирует, что она выдаст тот же результат при одном и том же входе. Хотя этот пример может показаться немного бессмысленным, вы можете использовать подобный код для генерации одного и того же ключа снова и снова.
.onAppear {
var hash = Hasher()
hash.combine(die.id)
print("hash \(hash.finalize()) \(die.hashValue)")
}
Справочная страница здесь в Apple дает лучшее практическое применение этого протокола.
Сопоставимые
Следующим в моем списке протоколов является сопоставимый, который выглядит почти так же, как Equatable.
extension Dice: Comparable {
static func < (lhs: Dice, rhs: Dice) -> Bool {
lhs.value < rhs.value
}
}
С этим кодом, добавленным в наш интерфейс SwiftUI, чтобы использовать новый протокол/свойство.
if dice.count == 2 {
if dice.first! > dice.last! {
Text("Winner 1st")
} else {
Text("Winner 2nd")
}
}
Но здесь есть оговорка. Поскольку мне пришлось указать на id, чтобы соответствовать протоколу Hashable, я не могу использовать == таким же образом.
if dice.first! == dice.last! {
Text("Unequal \(dice.hashValue)")
} else {
Text("Equal \(dice.hashValue)")
}
Чтобы обойти/исправить это, мне нужен новый оператор. Исправление для приведенного выше требует, чтобы я создал новый инфиксный оператор, такой как ====.. Очевидно, мне нужно обновить фрагмент кода, показанный выше, чтобы использовать ==== вместо показанного ==.
infix operator ==== : DefaultPrecedence
extension Dice {
static func ====(lhs: Dice, rhs: Dice) -> Bool {
lhs.face == rhs.face
}
}
Я уверен, что Apple хотела бы, чтобы вы использовали протоколы в своем повседневном коде, чтобы сделать более очевидным то, что вы пытаетесь сделать, расширение типов, которые вы можете использовать в своих пользовательских объектах.
равный
Хорошо, я признаю, что инфиксный оператор не для всех — в частности, для пуритан Swift. Итак, вот альтернатива, использующая более уравновешенный и без инфикса.
В нем я определяю представление как соответствующее протоколу уравновешивания, чтобы нацеливаться на лицевую сторону моего игрального кубика в нем.
Примечание. Я использовал .onAppear для первоначальной установки die1 и die2, а затем .onChange для управления всеми последующими перезагрузками костей каждый раз, когда я бросал новую пару.
На этом я заканчиваю эту статью. Если вы хотите скачать весь проект, который я использовал для сборки разных кейсов за один раз, — вы можете найти его на битовом ведре здесь.