Reputation:
For structs, Swift auto synthesizes the hashValue for us. So I am tempting to just use it to conform to the identifiable.
I understand that hashValue is many to one, but ID must be one to one. However, hash collision is very rare and I have faith in the math that it's most likely never going to happen to my app before I die.
I am wondering is there any other problems besides collision?
This is my code:
public protocol HashIdentifiable: Hashable & Identifiable {}
public extension HashIdentifiable {
var id: Int {
return hashValue
}
}
Upvotes: 6
Views: 2977
Reputation: 942
using hashValue
for id
is a bad idea !
for example you have 2 structs
struct HashID: HashIdentifiable {
var number: Int
}
struct NormalID: Identifiable {
var id = UUID()
var number: Int
}
when number
is changed:
HashID
's id
will be changed as well makes SwiftUI
thinks that this is completely new item and old one is goneNormalID
's id
stays the same, so SwiftUI
knows that item only modified its propertyIt's very important to let SwiftUI
knows what's going on, because it will affect animations, performance, ... That's why using hashValue
for id
makes your code looks bad and you should stay away from it.
Upvotes: 6
Reputation:
I am wondering is there any other problems besides collision?
Yes, many.
E.g., with this…
struct ContentView: View {
@State private var toggleData = (1...5).map(ToggleDatum.init)
var body: some View {
List($toggleData) { $datum in
Toggle(datum.display, isOn: $datum.value)
}
}
}
struct ToggleDatum: Identifiable & Hashable {
var value: Bool = false
var id: Int { hashValue }
var display: String { "\(id)" }
init(id: Int) {
// Disregard for now.
}
}
struct ToggleDatum: Identifiable & Hashable {
var value: Bool = false
var id: Int { hashValue }
let display: String
init(id: Int) {
self.display = "\(id)"
}
}
However, while it is not acceptable to use the hash value as an identifier, it is fine to do the opposite: use the identifier for hashing, as long as you know the IDs to be unique for the usage set.
/// An `Identifiable` instance that uses its `id` for hashability.
public protocol HashableViaID: Hashable, Identifiable { }
// MARK: - Hashable
public extension HashableViaID {
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
struct ToggleDatum: HashableViaID {
var value: Bool = false
let id: Int
var display: String { "\(id)" }
init(id: Int) {
self.id = id
}
}
This protocol works perfectly for Equatable
classes, as classes already have a default ID ready for use.
extension Identifiable where Self: AnyObject {
public var id: ObjectIdentifier {
return ObjectIdentifier(self)
}
}
Not that I at all recommend using a reference type for this example, but it would look like this:
public extension Equatable where Self: AnyObject {
static func == (class0: Self, class1: Self) -> Bool {
class0 === class1
}
}
class ToggleDatum: HashableViaID {
Upvotes: 1