问题描述
我有两个符合一个协议的结构。我创建了具有关联类型的枚举作为结构类型。当我尝试使用动态成员查找访问协议类型 A 的属性时,getter 工作正常。但是 Set 方法抛出错误:根类型为 'A' 的关键路径不能应用于类型为 'A1'/'A2' 的基
有时我也会收到此错误:无法通过下标赋值:'a' 是不可变的
import Foundation
protocol A {
var id: String { get }
}
struct A1 : A {
var id: String
var type: String
}
struct A2 : A {
var id: String
var test: String
}
@dynamicmemberLookup
enum AType {
case a1(A1)
case a2(A2)
}
extension AType {
subscript<T>(dynamicmember keyPath: WritableKeyPath<A,T>) -> T {
get {
switch self{
case .a1(let a):
return a[keyPath: keyPath]
case .a2(let a):
return a[keyPath: keyPath]
}
}
set {
switch self{
case .a1(var a):
a[keyPath: keyPath] = newValue
case .a2(var a):
a[keyPath: keyPath] = newValue
}
}
}
}
let a1struct = A1(id: "123",type: "Test")
var atype = AType.a1(a1struct)
print(atype.id)
任何想法都会有所帮助
解决方法
首先,可能是转录错误,但需要设置 id
才能使其有意义。
protocol A {
var id: String { get set } // <=== Add set
}
对于setter,有两个问题;一个非常清楚,另一个有点微妙。明显的问题是,即使这有效,它也无济于事:
case .a1(var a):
a[keyPath: keyPath] = newValue
这会制作相关数据的副本,然后修改该副本,然后将其丢弃。您需要的是(尽管此代码也不起作用):
case .a1(var a):
a[keyPath: keyPath] = newValue
self = .a1(a)
您需要修改 a
,然后创建一个新的枚举值来保存它。
更微妙的问题是类型 WritableKeyPath<A,T>
。这表示“根植于类型 A 的可写 KeyPath”。你的意思是“一个可写的 KeyPath 根植于 符合 到 A 的类型”,这不是一回事。要使此代码起作用,案例需要类似于 .a1(A)
而不是 .a1(A1)
。
Swift 确实允许您读取以类型符合的协议为根的 KeyPath。但它不会让你写通过它。有关简短讨论,请参阅 WritableKeyPath + inout argument of placeholder type doesn't compile。
你写“符合A的东西”的方式是:
subscript<Container: A,T>(dynamicMember keyPath: WritableKeyPath<Container,T>) -> T {
但是,这行不通,因为 Container 的类型是由调用者决定的,而且这必须接受任何传递的符合 A 的类型(您不能这样做)。
我不知道如何在没有 as!
强制转换的情况下解决此问题,但可以通过以下方式完成:
set {
switch self{
case .a1(var a as A):
a[keyPath: keyPath] = newValue
self = .a1(a as! A1)
case .a2(var a as A):
a[keyPath: keyPath] = newValue
self = .a2(a as! A2)
}
}
(我可能会建议在这里重新考虑使用枚举,看看是否可以直接使用 A1 和 A2 并使用 is
而不是 switch
来区分它们。Swift 枚举通常不是就像我们希望将它们与结构相比一样强大。但有时枚举仍然是最好的。)