Swift 键入擦除尝试:"Reference to invalid associated type"

Swift Type Erasure attempt: "Reference to invalid associated type"

我正在做一个伪造的练习来尝试实现类型擦除的容器。

import Foundation

protocol MoverType {
    func move()
}
extension MoverType {
    func move() { print("\(type(of: self)) is moving") }
}

class Slithering: MoverType {}
class Walking: MoverType {}
class Trotting: MoverType {}

protocol Animal {
    associatedtype Mover: MoverType
    var mover: Mover { get }
}

class Snake: Animal {
    let mover = Slithering()
}
class Dog: Animal {
    let mover = Trotting()
}
class Human: Animal {
    let mover = Walking()
}

class AnyAnimal: Animal {  // ERROR: Type 'AnyAnimal' does not conform to protocol 'Animal'
    var _mover: () -> Mover
    init<A: Animal>(animal: A) where Mover == A.Mover {
        _mover = {
            return animal.mover
        }
    }

    // ERROR HERE: "Reference to invalid associated type "Mover" of of type "AnyAnimal"
    var mover: Mover { fatalError() }
}

let d = AnyAnimal(animal: Dog())
let s = AnyAnimal(animal: Snake())

let animals = [d, s]   // Array<AnyAnimal>
for a in animals {
    a.mover.move()
}

我故意不希望我的 AnyAnimal 容器成为 AnyAnimal<T> 容器。因为,我希望能够在 Array<AnyAnimal>.

中存储许多 Animal 个实例

但是,正如您在上面的代码中看到的,编译器抱怨 AnyAnimal class。据我了解,Mover 的协议要求将由 AnyAnimal 初始值设定项中的通用 where 子句解决。

请帮助我了解缺少的内容。 (或者,是否有可能首先创建一个非通用类型擦除包装器?)

您的代码无法编译,因为需要在编译时通过为 Mover 协议提供具体实现来解析关联类型。

你可以做的是同时删除 MoverType 协议:

struct AnyMover: MoverType {
    private let mover: MoverType

    init(_ mover: MoverType) {
        self.mover = mover
    }

    func move() {
        mover.move()
    }
}

class AnyAnimal: Animal {
    let mover: AnyMover

    init<A: Animal>(animal: A) {
        mover = AnyMover(animal.mover)
    }    
}

我同意 Cristik 关于添加 AnyMover 类型橡皮擦的观点。这就是按照字面上的意思去做的方法。但如果你走这条路,你通常会错误地设计你的协议。例如,虽然我知道这是捏造的,但它是错误设计协议的一个很好的例子。 Animal 协议几乎可以肯定是:

protocol Animal {
    func move()
}

然后您的 associatedtype 和相关问题就烟消云散了。而且您的界面更有意义。动物可以移动。这不是什么 "with a Mover." 我希望这种类型的每次使用看起来都与您的示例几乎相同:animal.mover.move()。也就是说 mover 是调用者不应该关心的实现细节。

[Animal] 的循环中,您还能 做什么?您如何编写使用 .mover 没有 调用 .move 的通用代码?没有任何其他方法。

我知道这是捏造的,但这种情况在很多真实案例中都是真实存在的,大家要提高警惕。当您发现自己伸手去拿打字橡皮时,尤其是当您开始伸手去拿两层打字橡皮时,您需要问问自己是否做错了什么。并非总是如此,但大多数时候,你面前的问题都有更好的解决方案。

顺便说一句,这里的另一种方法是保留 Mover,但只是让它们成为协议。来电者真的想知道爬行者和滑行者之间的区别吗?隐藏这不是整个目标吗?如果是这样,你可以这样走:

protocol Animal {
    var mover: MoverType { get }
}

再一次,所有的问题都烟消云散了。

无论哪种方式,如果有 mover 可用,您仍然可以自动实现 move() 方法。例如,您可以这样设计协议:

// Something that moves animals
protocol Mover {
    func move(animal: Animal)
}

// Something that has a mover
protocol MoverProviding {
    var mover: Mover { get }
}

// And of course Animals. They might be MoverProviding. They might not.
protocol Animal {
    func move()
}

// But if they *are* MoverProviding, we can use that.
extension Animal where Self: MoverProviding {
    func move() {
        mover.move(animal: self)
    }
}

当您键入 associatedtype 时,您通常会想 "this protocol is all about adding extra algorithms (methods and functions) to other types." 如果协议的重点是允许异构集合,那么您可能走错了路。类型橡皮擦有时很有用且很重要,但如果您觉得自己非常需要它们(并且 尤其是 如果您因为异构集合而需要它们),那么您可能遇到了设计问题。