在Swift中用不同类型覆盖超类属性

147

在Swift中,有人能解释一下如何用从原始属性继承的另一个对象的子类来覆盖超类上的属性吗?

以这个简单的例子为例:

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    override let chassis = RacingChassis() //Error here
}

这会导致错误:

Cannot override with a stored property 'chassis'

如果我将底盘变量名更改为“var”,则会出现错误:

Cannot override mutable property 'chassis' of type 'Chassis' with covariant type 'RacingChassis'

我在“覆盖属性”部分的指南中只找到了一件事,表明我们必须覆盖getter和setter,这可能适用于更改属性的值(如果它是'var'),但是更改属性类怎么办?

15个回答

127

Swift 不允许您更改任何变量或属性的类类型。相反,您可以在子类中创建一个额外的变量来处理新的类类型:

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    var chassis = Chassis()
}
class RaceCar: Car {
    var racingChassis = RacingChassis()
    override var chassis: Chassis {
        get {
            return racingChassis
        }
        set {
            if let newRacingChassis = newValue as? RacingChassis {
                racingChassis = newRacingChassis
            } else {
                println("incorrect chassis type for racecar")
            }
        }
    }
}

似乎无法使用let语法声明属性,并在其子类中用var进行覆盖,反之亦然。这可能是因为超类实现可能不希望该属性一旦初始化就发生变化。因此,在这种情况下,需要在超类中使用'var'声明属性以匹配子类(如上面的片段所示)。如果无法更改超类中的源代码,则最好在每次需要改变底盘时销毁当前RaceCar并创建一个新的RaceCar。


1
抱歉,我刚刚删除了我的评论,然后看到了你的回复。我遇到了一个问题,因为在我的实际情况中,我的超类是一个具有“strong”属性的Objective-C类,并且我尝试覆盖它时出现错误 - 但似乎我错过了它在Swift中转换为“隐式解包可选项”(chassis!),所以 override var chassis : Chassis! 可以解决它。 - James
4
在Xcode 6.1下,Swift 1.1不再支持此方法。会出现错误:“无法使用'var'的getter重写不可变的'let'属性'chassis'”。有更好的解决方案吗? - Darrarski
1
在Xcode 6.3 beta下,Swift 1.2也不再适用。无法使用协变类型'Type2'覆盖可变属性'Type1'的'A'。 - bubuxu
23
在我看来,这真的很糟糕,也是 Swift 的一种失败。由于 RacingChassis * 就是一个 Chassis,编译器理应可以允许您在子类中细化属性的类。许多语言都支持这种操作,而不支持会导致像这样丑陋的解决方案。不冒犯您 :/ - devios1
1
@devios1 我同意,Swift 5.5 刚刚发布了,现在我们有了 async-await,但很遗憾我们没有这个基本功能。 - Saad Rehman
显示剩余4条评论

12

这似乎起作用了

class Chassis {
    func description() -> String {
        return "Chassis"
    }
}
class RacingChassis : Chassis {
    override func description() -> String {
        return "Racing Chassis"
    }

    func racingChassisMethod() -> String {
        return "Wrooom"
    }
}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    override var chassis: RacingChassis {
    get {
        return self.chassis
    }
    set {
        self.chassis = newValue
    }
    }
}

var car = Car()
car.chassis.description()

var raceCar = RaceCar()
raceCar.chassis.description()
raceCar.chassis.racingChassisMethod()

1
这是有效的,因为在Car类中,chassis属性被定义为let,这使得它无法更改。我们只能在RaceCar类中更改chassis属性。 - David Arve
5
这无法适用于Swift 2.0。会出现“错误:无法使用'var'的getter覆盖不可变的'let'属性'chassis'”的提示。 - mohamede1945
这在Swift 4.0中也不起作用。Playground执行失败:错误:MyPlaygrounds.playground:14:15:错误:无法使用“var”的getter覆盖不可变的“let”属性“chassis” override var chassis: RacingChassis { ^MyPlaygrounds.playground:11:6:注意:尝试在此处覆盖属性 let chassis = Chassis() ^ - karim

12

试试这个:

class Chassis{
     var chassis{
         return "chassis"
     } 
}

class RacingChassis:Chassis{
     var racing{
         return "racing"
     } 
}

class Car<Type:Chassis> {
     let chassis: Type
     init(chassis:Type){
        self.chassis = chassis
     }
}

class RaceCar: Car<RacingChassis> {
     var description{
         return self.chassis.racing
     } 
}

那么:

let racingChassis = RacingChassis()
let raceCar = RaceCar(chassis:racingChassis)
print(raceCar.description) //output:racing

请参考http://www.mylonly.com/14957025459875.html中的详细内容。


啊,整洁干净。 - Inder Kumar Rathore

2

除了必须使用let关键字声明超类之外,Dash提供的解决方案很好。以下是一种可能但不建议使用的解决方案!

下面的解决方案将在Xcode 6.2、SWIFT 1.1(如果所有类都在不同的swift文件中)中编译通过,但应避免使用,因为它可能会导致意外行为(包括崩溃,特别是在使用非可选类型时)。注意:这在XCODE 6.3 BETA 3、SWIFT 1.2中不起作用

class Chassis {}
class RacingChassis : Chassis {}
class Car {
    var chassis:Chassis? = Chassis()
}

class RaceCar: Car {
    override var chassis: RacingChassis? {
        get {
            return super.chassis as? RacingChassis
        }
        set {
            super.chassis = newValue
        }
    }
}

1
它不适用于Swift 2.0。它会给出“无法使用协变类型'RacingChassis?'覆盖可变属性'type 'Chassis?'的'chassis'”。 - mohamede1945

2
您可以通过使用泛型来实现它:

class Descriptor {
    let var1 = "a"
}

class OtherDescriptor: Descriptor {
    let var2 = "b"
}

class Asset<D: Descriptor> {
    let descriptor: D

    init(withDescriptor descriptor: D) {
        self.descriptor = descriptor
    }

    func printInfo() {
        print(descriptor.var1)
    }
}

class OtherAsset<D: OtherDescriptor>: Asset<D> {
    override func printInfo() {
        print(descriptor.var1, descriptor.var2)
    }
}

let asset = Asset(withDescriptor: Descriptor())
asset.printInfo() // a

let otherAsset = OtherAsset(withDescriptor: OtherDescriptor())
otherAsset.printInfo() // a b

使用这种方法,您将拥有100%的类型安全代码,而不需要强制解包。
但是,这种方法有点像一个hack,如果您需要重新定义多个属性,那么您的类声明将变得非常混乱。因此,请小心使用此方法。

2
理论上,您可以用这种方式做...
class ViewController {

    var view: UIView! { return _view }

    private var _view: UIView!
}

class ScrollView : UIView {}

class ScrollViewController : ViewController {

    override var view: ScrollView! { return super.view as ScrollView! }
}

class HomeView : ScrollView {}

class HomeViewController : ScrollViewController {

    override var view: HomeView! { return super.view as HomeView! }
}

这在 Xcode playground 中可以完美运行。

但是,如果你在一个真实的项目中尝试这样做,编译器会报错:

声明“view”不能覆盖超过一个父类声明

截至目前,我只检查了 Xcode 6.0 GM。

不幸的是,你必须等到苹果公司解决这个问题。

我也提交了一个错误报告。 18518795


这是一个有趣的解决方案,它可以在6.1中工作。 - Chris Conover

1
我看到有很多理由说明使用变量而不是函数设计 API 是有问题的,使用计算属性对我来说感觉像是一个变通方法。保持实例变量的封装性是有好处的。在这里,我创建了一个汽车协议,Car 符合该协议。该协议有一个访问器方法返回一个底盘对象。由于 Car 符合该协议,RaceCar 子类可以覆盖它并返回一个不同的 Chassis 子类。这使得 Car 类可以编程到一个接口 (Automobile),而知道 RacingChassis 的 RaceCar 类可以直接访问 _racingChassis 变量。
class Chassis {}
class RacingChassis: Chassis {}

protocol Automobile {
    func chassis() -> Chassis
}

class Car: Automobile {
    private var _chassis: Chassis

    init () {
        _chassis = Chassis()
    }

    func chassis() -> Chassis {
        return _chassis
    }
}

class RaceCar: Car {
    private var _racingChassis: RacingChassis

    override init () {
        _racingChassis = RacingChassis()
        super.init()
    }

    override func chassis() -> Chassis {
        return _racingChassis
    }
}

另一个使用变量设计 API 失败的例子是当协议中存在变量时。如果您想将所有协议函数拆分为扩展,可以这样做,但存储属性无法放置在扩展中,必须在类中定义(要使其编译,您必须取消注释 AdaptableViewController 类中的代码并从扩展中删除 mode 变量)。
protocol Adaptable {
    var mode: Int { get set }
    func adapt()
}

class AdaptableViewController: UIViewController {
    // var mode = 0
}

extension AdaptableViewController: Adaptable {

    var mode = 0 // compiler error

    func adapt() {
        //TODO: add adapt code
    }
}

上述代码会出现编译错误:"Extensions may not have stored properties"。以下是您可以重新编写示例的方法,以便协议中的所有内容都可以通过使用函数在扩展中分离出来:
protocol Adaptable {
    func mode() -> Int
    func adapt()
}

class AdaptableViewController: UIViewController {
}

extension AdaptableViewController: Adaptable {
    func mode() -> Int {
        return 0
    }
    func adapt() {
        // adapt code
    }
}

1

根据您计划如何使用属性,最简单的方法是为子类使用可选类型,并重写超类的 didSet {} 方法:

class Chassis { }
class RacingChassis: Chassis { }

class Car {
    // Declare this an optional type, and do your 
    // due diligence to check that it's initialized
    // where applicable
    var chassis: Chassis?
}
class RaceCar: Car {
    // The subclass is naturally an optional too
    var racingChassis: RacingChassis?
    override var chassis: Chassis {
        didSet {
            // using an optional, we try to set the type
            racingChassis = chassis as? RacingChassis
        }
    }
}

显然,您需要花费一些时间来检查是否可以以这种方式初始化类,但是通过将属性设置为可选,您可以保护自己免受转换不再起作用的情况。

0
class Chassis {}
class RacingChassis : Chassis {}

class Car {
    fileprivate let theChassis: Chassis
    var chassis: Chassis {
        get {
            return theChassis
        }
    }
    fileprivate init(_ chassis: Chassis) {
        theChassis = chassis
    }
    convenience init() {
        self.init(Chassis())
    }
}
class RaceCar: Car {
    override var chassis: RacingChassis {
        get {
            return theChassis as! RacingChassis
        }
    }
    init() {
        super.init(RacingChassis())
    }
}

0

简单使用泛型,例如:

class Chassis {
    required init() {}
}
class RacingChassis : Chassis {}

class Car<ChassisType : Chassis> {
    var chassis = ChassisType()
}


let car = Car()
let racingCar = Car<RacingChassis>()
    
let c1 = car.chassis
let c2 = racingCar.chassis
    
print(c1) // Chassis
print(c2) // RacingChassis

此外,底盘甚至不需要成为子类:
protocol Chassis {
    init()
}
class CarChassis: Chassis{
    required init() {
    }
}
class RacingChassis : Chassis {
    required init() {
    }
}

class Car<ChassisType : Chassis> {
    var chassis = ChassisType()
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接