Приравниваемые, сопоставимые, идентифицируемые и хэшируемые решения
Протоколы, безусловно, не новы для 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
для управления всеми последующими перезагрузками костей каждый раз, когда я бросал новую пару.
На этом я заканчиваю эту статью. Если вы хотите скачать весь проект, который я использовал для сборки разных кейсов за один раз, — вы можете найти его на битовом ведре здесь.