Swift 5.7 - 使存在类型的 'any' 协议符合 Hashable

4

我对符合存在变量方面有些困惑。

由于在此处 Animal 总是符合 Hashable,所以我想象任何 Animal必须符合 Hashable

然而,我看到的错误使我开始怀疑。是否有人可以帮忙解释为什么会这样,并且如果可能的话,帮我解决这个问题?

import SwiftUI
import PlaygroundSupport

protocol Animal: Hashable {
    var name: String { get }
}

struct Cow: Animal {
    let name: String
}

struct Chicken: Animal {
    let name: String
}

let anAnimalList: [any Animal] = [Cow(name: "Aaron"), Chicken(name: "Billy"), Cow(name: "Charlie"), Chicken(name: "Delilah")]

struct myView: View {
    @State private var anAnimal: (any Animal)?
    
    var body: some View {
        VStack {
            List(anAnimalList, id: \.self, selection: $anAnimal) { animal in // ERROR: Type 'any Animal' cannot conform to 'Hashable'
                Text("Animal")
            }
            
            Text("Animal is \(anAnimal?.name ?? "Null")")
        }
    }
}

PlaygroundPage.current.setLiveView(myView())


1
你在这里想要实现什么? - Steven-Carrot
它甚至不必是“任何”。 - cora
1个回答

3
假设我们想找到一种列出任何动物但不是“为什么会发生这种事情”的方法 :)(请参阅相应的WWDC22会话以找到第二个答案)
在Xcode 14b3 / iOS 16上进行了测试

demo

我来翻译一下:

让我们跳进这个洞里...

原始代码中有两个报告错误的地方:

List(anAnimalList, 
                   id: \.self,     // << 1st place
            selection: $anAnimal   // << 2nd place
) { animal in
    Text("Animal")
}

第一名 - 因为Animal不可识别,而self与具体类型的实例相关,所以我们不应该使用\.self(因为编译器无法推断具体类型),而是直接通过\.id进行标识。

因此,这里是第一个问题的可能解决方法:

protocol Animal: Hashable, Identifiable {
    var id: UUID { get }
    var name: String { get }
}

struct Cow: Animal {
    let id = UUID()
    let name: String
}

struct Chicken: Animal {
    let id = UUID()
    let name: String
}

    var body: some View {
        VStack {
            List(anAnimalList, id: \.id) { animal in   // << no error !!
                Text("Animal: \(animal.name)")
            }
        }
    }

第二名 - 现在谈论选择;故事是一样的,Swift 应该知道“具体类型的选择”,或者能够从代码中“推断出来”,但这两种方法在编译时都是不可能的,所以与其试图用头撞墙,不如找到一扇门...

一个可能的解决方案是在上下文中使用已知且具体的东西进行选择... 我们刚刚在上面添加了它 - “id”

因此,这里是第二部分的修复。

@State private var anAnimalID: UUID? // << here !!

var body: some View {
    VStack {
        List(anAnimalList, id: \.id, selection: $anAnimalID) { animal in
            Text("Animal: \(animal.name)")
        }
        if let animal = anAnimalList.first { $0.id == anAnimalID } {
            Text("Animal is \(animal.name)")
        }
    }
}

在 GitHub 上测试代码


我相信我们都有一颗钻石头脑。 - Quang Hà
谢谢!这对我的理解有很大帮助。还有其他可能的“报告错误的第二个位置”的解决方案吗?拥有UUID有点有用,但我发现它有点限制性,最终我会传递一个UUID而不是完整的对象。你知道为什么不能改成“某个动物”吗?因为那应该是特定协议的实例? 例如:@State private var anAnimal: (some Animal)?当我尝试运行时,我得到“无法推断出通用参数'tau_0_0'”-我认为这是Xcode14b3的奇怪错误。 - ibmaul
因为您认为它是运行时功能,但将any解析为some是编译时过程,所以请将其视为如果您无法立即确定此处的具体类型(从代码中而不是在运行时),那么编译器也无法这样做。 - Asperi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接