【问题标题】:Swift generics not preserving typeSwift 泛型不保留类型
【发布时间】:2014-10-09 13:49:25
【问题描述】:

我正在尝试根据指定的泛型类型强制转换和/或生成变量。我知道 swift 中没有类型擦除,但除了泛型的指定条件之外,泛型似乎不保留类型,例如符合基类。似乎我可以转换或初始化的只是基类。更奇怪的是,当我在调试器中时,泛型似乎有一个指向正确类的 RawPointer,甚至变量看起来都是正确的类型:

编辑:

从 Xcode 6.1 开始,这仍然是一个问题(简化代码由 Gregory Higley 提供):

class BaseClass {
    func printme() -> Void {
        println("I am BaseClass")
    }
}

class DerivedClass : BaseClass {
    override func printme() -> Void {
        println("I am DerivedClass")
    }
}

class Util<T: BaseClass> {
    func doSomething() {
        var instance = T()
        instance.printme()
    }
}

var util = Util<DerivedClass>()
util.doSomething()

仍然打印出“I am BaseClass”

还要注意,基类中所需的 init{} 不再起作用。

【问题讨论】:

  • 你应该把正确答案改成 rintaro's。
  • @rfrittelli - 实际上正确的答案是 JeremyP 在下面。我已经确认它有效,并且 rintaro 的答案在最新的 XCode 中不再有效。
  • 您的原始输出是什么(它会有所帮助)?从 6.1 开始,两个测试都会产生相同的结果,这是预期的。你也能说出你的问题吗?
  • 我对我的帖子进行了编辑。但我的问题是它是错误的以及如何解决。取决于你如何看待它似乎是一个错误。

标签: swift xcode6 generics


【解决方案1】:

此代码按预期工作。

class BaseClass {

    required init() {} // <-- ADDED THIS

    func printme() -> Void {
        println("I am BaseClass")
    }
}

class DerivedClass : BaseClass {
    override func printme() -> Void {
        println("I am DerivedClass")
    }
}

class Util<T: BaseClass> {
    func doSomething() {
        var instance = T()
        instance.printme()
    }
}

var util = Util<DerivedClass>()
util.doSomething()

代码库是从@GregoryHigley 答案中偷来的 :)

将init() {} 标记为required 做到了。 这保证了init() 是来自BaseClass 的ANY 派生类的指定初始化器。

没有它,可以创建非法子类,例如:

class IllegalDerivedClass : BaseClass {
    var name:String

    init(name:String) {
        self.name = name
        super.init()
    }

    override func printme() -> Void {
        println("I am DerivedClass")
    }
}

var util = Util<IllegalDerivedClass>()
util.doSomething()

你知道这行不通,因为IllegalDerivedClass doesn't inherit init() initializer。

我想,这就是你的问题的原因。

到底是谁的错?

  • 编译器应该警告歧义。
  • 运行时应尝试按照T 的指定初始化DerivedClass()。
  • 调试器应该显示instance 是BaseClass 的一个实例,因为它实际上是。

添加:

从 Xcode 6.1 GM 2 开始,您似乎需要做更多的工作。 (除了required init() {})

class Util<T: BaseClass> {
    let theClass = T.self // store type itself to variable

    func doSomething() {
        var instance = theClass() // then initialize
        instance.printme()
    }
}

我完全不知道我们为什么需要这个,X 发生了什么(

添加:2014/10/18

我发现这也有效:

    func doSomething() {
        var instance = (T.self as T.Type)()
        instance.printme()
    }

添加:2015/02/10

截至 Xcode 版本 6.3 (6D520o) / Swift 1.2

我们不再需要(T.self as T.Type)() hack。只要 T 有 required init() 初始化器,T() 就可以工作。

class Util<T: BaseClass> {
    func doSomething() {
        var instance = T()
        instance.printme()
    }
}

【讨论】:

  • 这应该被标记为正确答案。但遗憾的是,我仍然认为这是一个错误,因为它违反了最小意外原则。让它正常工作需要专业知识,而这并不是必需的。
  • 很酷的好答案。但我同意这仍然是一个错误。
  • -1 因为它不适用于最新的 XCode (6.1 GM2) 我认为 @JeremyP 的答案有效。
  • @LeeWhitney 感谢您提供 Xcode6.1 信息。我没有测试过。
  • 我在使用 (T.self as T.type) 或之前建议的变体时遇到问题。对我来说唯一的区别是我的初始化程序采用单个参数。当我尝试创建 T 类型的实例时,出现“错误访问”崩溃。有其他人看过这个吗?
【解决方案2】:

我创建了您的代码的简化版本,如下所示:

class BaseClass {
    func printme() -> Void {
        println("I am BaseClass")
    }
}

class DerivedClass : BaseClass {
    override func printme() -> Void {
        println("I am DerivedClass")
    }
}

class Util<T: BaseClass> {
    func doSomething() {
        var instance = T()
        instance.printme()
    }
}

var util = Util<DerivedClass>()
util.doSomething()

这将问题提炼为本质。人们会期望util.doSomething() 打印“I am DerivedClass”,但它每次都打印“I am BaseClass”。这一定是一个错误,因为没有任何合理的类型系统会以这种方式工作。

我认为您应该将此作为错误提交给 Apple。

【讨论】:

  • 是的,对于我令人困惑的示例感到抱歉,但这正是我的想法。谢谢,会做的。
  • 当您提交错误时,请使用我的示例而不是您的示例,因为它更简单。 (无需功劳。毕竟你发现了这个错误。)
  • 我确实用过你的 :)。再次感谢!
  • 问题是var instance = T()初始化器不是虚拟的,所以实例总是用BaseClass()创建的。我要写一个答案,里面有一个解决方法。
  • 还有一个问题。如果我说 func doSomething(t: T) -> Void { val newT = T() } 并且 Utility 类没有采用通用参数,则应该根据我正确推断 T 的类型传入正确吗?这似乎也被打破了。
【解决方案3】:

问题是var instance = T() 初始化器不是虚拟的,所以实例总是使用BaseClass()* 创建的。下面的代码使用一个类函数来解决这个问题:

class BaseClass {
    func printme() -> String {
        return "I am BaseClass"
    }
    class func makeInstance() -> BaseClass
    {
        return BaseClass()
    }
}

class DerivedClass : BaseClass {
    override class func makeInstance() -> BaseClass
    {
        return DerivedClass()
    }

    override func printme() -> String {
        return "I am DerivedClass"
    }
}

class Util<T: BaseClass> {
    func doSomething() -> String {
        var instance = T.makeInstance()
        return instance.printme()
    }
}

var util = Util<DerivedClass>()
println("\(util.doSomething())")

我更改了printme() 的实现,只是因为原始代码由于某种原因没有在操场上打印任何内容。

* 我认为这仍然是一个错误。

【讨论】:

  • 我曾想过这个......但对我来说它似乎仍然破碎。为什么类型不能推断自身并调用自身的 init?
  • @rfrittelli 我认为这也是一个错误。我只是在解释什么是错误,恕我直言
  • 是的,你不应该跳过这些圈子来做这件事。它违反了“最小惊喜原则”。我对 Swift 的类型系统有很多批评,尤其是涉及泛型和扩展的地方。似乎有很多“减速带”。
  • @JeremyP - 编辑添加 makeTypeInstance() ,它返回 T() 所以以后使用不需要转换。
  • @Lee 对不起,但我要改回来,因为更改与问题无关。另外,您在我的答案末尾添加的评论是错误的。创建了一个 T is 的实例,只是编译器只知道它是一个 BaseClass
【解决方案4】:

我遇到了类似的问题。您需要添加required 初始化器和let realType = T.self 并将T() 替换为realType()。

class BaseClass {
    required init() {}
    func printme() -> Void {
        println("I am BaseClass")
    }
}

class DerivedClass : BaseClass {
    override func printme() -> Void {
        println("I am DerivedClass")
    }
}

class Util<T: BaseClass> {
    func doSomething() {
        let realType = T.self // that's it
        var instance = realType()
        instance.printme()
    }
}

var util = Util<DerivedClass>()
util.doSomething()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-10
    • 1970-01-01
    • 2011-07-02
    • 2022-07-21
    相关资源
    最近更新 更多