Swift - 改变枚举关联类型属性的值

问题描述

我有两个符合一个协议的结构。我创建了具有关联类型的枚举作为结构类型。当我尝试使用动态成员查找访问协议类型 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 枚举通常不是就像我们希望将它们与结构相比一样强大。但有时枚举仍然是最好的。)