Is it possible to restrict a Swift generic class function return type to the same class or subclass?

随声附和 提交于 2020-01-09 10:52:49

问题


I am extending a base class (one which I do not control) in Swift. I want to provide a class function for creating an instance typed to a subclass. A generic function is required. However, an implementation like the one below does not return the expected subclass type.

class Calculator {
    func showKind() { println("regular") }
}

class ScientificCalculator: Calculator {
    let model: String = "HP-15C"
    override func showKind() { println("scientific") }
}

extension Calculator {
    class func create<T:Calculator>() -> T {
        let instance = T()
        return instance
    }
}

let sci: ScientificCalculator = ScientificCalculator.create()
sci.showKind()

The debugger reports T as ScientificCalculator, but sci is Calculator and calling sci.showKind() returns "regular".

Is there a way to achieve the desired result using generics, or is it a bug?


回答1:


Ok, from the developer forums, if you have control of the base class, you might be able to implement the following work around.

class Calculator {
    func showKind() { println("regular") }
    required init() {}
}

class ScientificCalculator: Calculator {
    let model: String = "HP-15C"
    override func showKind() { println("\(model) - Scientific") }
    required init() {
        super.init()
    }
}

extension Calculator {
    class func create<T:Calculator>() -> T {
        let klass: T.Type = T.self
        return klass()
    }
}

let sci:ScientificCalculator = ScientificCalculator.create()
sci.showKind()

Unfortunately if you do not have control of the base class, this approach is not possible.




回答2:


There is a bug somewhere - I mean, not in your code :)

This is obviously a generic method:

class func create<T:Calculator>() -> T

T is inferred by the type of the variable the return value is assigned to, which in your case is ScientificCalculator.

A more interesting case. Let's modify the create function such that the type of T is made explicit, regardless of the type of the variable the instance is assigned to:

class func create<T:Calculator>(type: T.Type) -> T

let sci: ScientificCalculator = ScientificCalculator.create(ScientificCalculator.self)

The result is the same.

A more interesting observation: sci is a variable of ScientificCalculator type, but it points to an instance of Calculator. So this code:

sci.model

compiles, but generates a runtime exception - because there's no model property defined in the base class.

This is clearly a compiler bug: an instance of a superclass is assigned to a variable whose type is one of its subclasses - that should never be possible (although the opposite is possible)

Also read my answer to a similar question




回答3:


Update, i used Calculator as return type which makes no sense. It should be Self, but still a required init has to exist:

class Calculator {
    func showKind() { println("regular") }
    required init() {

    }
}

class ScientificCalculator: Calculator {
    let model: String = "HP-15C"
    override func showKind() { println("scientific") }
}

extension Calculator {
    class func create() -> Self {
        return self()
    }
}

var b = ScientificCalculator.create() // HP-15C
b.showKind()

I addition to Sean Woodward, it will work if Calculators have (required) initializers. Overriding that in a subclass is not needed.

class Calculator {
    required init() {

    }
    func showKind() { println("regular") }
}

class ScientificCalculator: Calculator {
    let model: String = "HP-15C"
    override func showKind() { println("scientific") }
}

extension Calculator {
    class func create<T:Calculator>( type:T.Type ) -> T {
        return type() as T
    }
    class func newInstance() -> Calculator {
        return self()
    }
}

var b = ScientificCalculator.newInstance() // HP-15C
b.showKind() // sientific



回答4:


This doesn't explain why your generic approach isn't working. I'm just looking for an alternative.

It seems with Swift that they have replaced factory methods with convenience initializers. You can create a new convenience initializer and add that with an extension to Calculator.

extension Calculator {
    convenience init(name: String) {
        println(name)
        self.init()
    }
}

let reg = Calculator(name: "Reg")
reg.showKind()  // "regular"

let sci = ScientificCalculator(name: "Sci")
sci.showKind()  // "scientific"


来源:https://stackoverflow.com/questions/27336607/is-it-possible-to-restrict-a-swift-generic-class-function-return-type-to-the-sam

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!