SwiftUI 在 Identificate 中使用 hashValue 可以吗

6 ios swiftui

对于结构体,Swift 会自动为我们合成 hashValue。所以我很想用它来符合可识别性。

我知道hashValue是多对一的,但ID必须是一对一的。然而,哈希冲突非常罕见,而且我相信在我死之前我的应用程序很可能永远不会发生这种情况。

我想知道除了碰撞之外还有其他问题吗?

这是我的代码:

public protocol HashIdentifiable: Hashable & Identifiable {}

public extension HashIdentifiable {
  var id: Int {
    return hashValue
  }
}
Run Code Online (Sandbox Code Playgroud)

meo*_*meo 6

使用hashValueforid是一个坏主意!

例如你有 2 个结构

struct HashID: HashIdentifiable {
    var number: Int
}

struct NormalID: Identifiable {
    var id = UUID()
    var number: Int
}
Run Code Online (Sandbox Code Playgroud)

何时number更改:

  • HashID也会id被改变,让人SwiftUI认为这是全新的物品,旧的已经消失了
  • NormalID保持id不变,所以SwiftUI知道该项目仅修改了其属性

SwiftUI让知道发生了什么非常重要,因为它会影响动画、性能……这就是为什么使用hashValueforid会让你的代码看起来很糟糕,你应该远离它。


小智 1

\n

我想知道除了碰撞之外还有其他问题吗?

\n
\n

是的,很多。

\n

例如,用这个\xe2\x80\xa6

\n
struct ContentView: View {\n  @State private var toggleData = (1...5).map(ToggleDatum.init)\n\n  var body: some View {\n    List($toggleData) { $datum in\n      Toggle(datum.display, isOn: $datum.value)\n    }\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 碰撞会导致视觉完全混乱。
  2. \n
\n
struct ToggleDatum: Identifiable & Hashable {\n  var value: Bool = false\n  var id: Int { hashValue }\n  var display: String { "\\(id)" }\n\n  init(id: Int) {\n    // Disregard for now.\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 没有碰撞,加上不稳定的标识符,会破坏你的“身份”契约并杀死动画。性能也会受到影响,但是当它如此丑陋时,甚至在他们注意到任何缓慢或电池耗尽之前,没有人会关心这一点。
  2. \n
\n
struct ToggleDatum: Identifiable & Hashable {\n  var value: Bool = false\n  var id: Int { hashValue }\n  let display: String\n\n  init(id: Int) {\n    self.display = "\\(id)"\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

然而,虽然使用哈希值作为标识符是不可接受的,但可以采取相反的做法:使用标识符进行哈希,只要您知道 ID 对于使用集来说是唯一的。

\n
/// An `Identifiable` instance that uses its `id` for hashability.\npublic protocol HashableViaID: Hashable, Identifiable { }\n\n// MARK: - Hashable\npublic extension HashableViaID {\n  func hash(into hasher: inout Hasher) {\n    hasher.combine(id)\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
struct ToggleDatum: HashableViaID {\n  var value: Bool = false\n  let id: Int\n  var display: String { "\\(id)" }\n\n  init(id: Int) {\n    self.id = id\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

该协议非常适合Equatable类,因为类已经有一个可供使用的默认 ID 。

\n
extension Identifiable where Self: AnyObject {\n  public var id: ObjectIdentifier {\n    return ObjectIdentifier(self)\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我并不建议在此示例中使用引用类型,但它看起来像这样:

\n
public extension Equatable where Self: AnyObject {\n  static func == (class0: Self, class1: Self) -> Bool {\n    class0 === class1\n  }\n}\n\nclass ToggleDatum: HashableViaID {\n
Run Code Online (Sandbox Code Playgroud)\n