Home > Back-end >  SwiftUI is it ok to use hashValue in Identifiable
SwiftUI is it ok to use hashValue in Identifiable

Time:03-06

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
  }
}

CodePudding user response:

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)
    }
  }
}
  1. Collisions cause complete visual chaos.
struct ToggleDatum: Identifiable & Hashable {
  var value: Bool = false
  var id: Int { hashValue }
  var display: String { "\(id)" }

  init(id: Int) {
    // Disregard for now.
  }
}
  1. No collisions, with unstable identifiers, breaks your contract of "identity" and kills animation. Performance will suffer too, but nobody will care about that when it's so ugly even before they notice any slowness or battery drain.
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 {

CodePudding user response:

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 gone
  • NormalID's id stays the same, so SwiftUI knows that item only modified its property

It'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.

  • Related