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


129

在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'),但是更改属性类又如何呢? ?

Answers:


115

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类,尝试覆盖它时遇到了错误-但似乎我错过了将其转换为“隐式展开的可选”(chassis!) Swift,因此进行override var chassis : Chassis!修复。
2014年

4
这在Xcode 6.1下的Swift 1.1不再有效。生成错误:“无法使用'var'的getter覆盖不可变的'let'属性'chassis'”。有更好解决方案的想法吗?
2014年

1
在Xcode 6.3 beta下的Swift 1.2中也不再起作用。无法使用协变类型'Type2'覆盖类型'Type1'的可变属性'
A'– bubuxu

10
这真是la脚,也是Swift,imo的失败。由于RacingChassis 机箱,因此编译器应该没有问题,可以让您在子类中优化属性的类。许多语言都允许这样做,并且不支持它会导致类似的丑陋解决方法。没有冒犯的意思。:/
devios1 '16

1
可选参数提供了一种建议,表明可能不存在变量,因为该变量是由不再返回期望值的函数提供的。如果功能被覆盖,则可能发生这种情况。看看我的答案以了解详细信息。

10

这似乎有效

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
由于机箱属性是letCar类中定义的,因此无法更改,因此可以使用。我们只能chassisRaceCar类上更改属性。
David Arve 2014年

4
这不适用于Swift 2.0。它给出了“错误:无法使用'var'的获取方法来覆盖不可变的'let'属性'chassis'”
mohamede1945

这在swift 4.0中也不起作用。临时执行失败:错误:MyPlaygrounds.playground:14:15:错误:无法使用'var'的吸气剂覆盖不可变的'let'属性'chassis'覆盖var底盘:RacingChassis {^ MyPlaygrounds.playground:11:6:注意:尝试在此处覆盖属性让机箱= Chassis()^
karim

7

试试这个:

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

3

提供的Solution Dash效果很好,除了必须使用let关键字而不是var声明超类。这是可能的解决方案,但不推荐!

下面的解决方案将与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。它给出“无法覆盖'Chassis类型的可变属性'chassis'?” 与协变类型'
RacingChassis

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游乐场中完美工作。

但是,如果您在实际项目中尝试此操作,则编译器错误会告诉您:

声明“视图”不能覆盖多个父类声明

到目前为止,我只检查了Xcode 6.0 GM。

不幸的是,您必须等到Apple修复此问题。

我也提交了错误报告。18518795


这是一个有趣的解决方案,并且可以在6.1中使用。
克里斯·康诺夫

1

我已经看到了很多原因,为什么使用变量而不是函数来设计API是有问题的,对我来说,使用计算属性似乎是一种解决方法。有充分的理由将实例变量封装起来。在这里,我创建了Car遵循的汽车协议。该协议具有一个访问器方法,该方法返回一个机箱对象。由于Car符合它,所以RaceCar子类可以覆盖它并返回不同的Chassis子类。这使Car类可以编程到接口(汽车),而知道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
    }
}

上面的代码将出现此编译器错误:“扩展名可能没有存储的属性”。您可以按照以下方式重新编写上面的示例,以便可以使用函数代替扩展中的协议中的所有内容:

protocol Adaptable {
    func mode() -> Int
    func adapt()
}

class AdaptableViewController: UIViewController {
}

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

1

您可以使用泛型来实现:

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,如果您需要重新定义几个属性,则类声明看起来会很混乱。因此,请谨慎使用此方法。


1

根据您计划使用该属性的方式,最简单的方法是为您的子类使用可选类型,并didSet {}为super 覆盖该方法:

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

您可以简单地创建RacingChassis的另一个变量。

class Chassis {}
class RacingChassis : Chassis {}
class Car {
    let chassis: Chassis
    init(){
        chassis = Chassis()
}}

class RaceCar: Car {
let raceChassis: RacingChassis
init(){
        raceChassis = RacingChassis()
}}

这行得通,但是我认为Dash的答案通过覆盖chassis并允许我们RacingChassis使用该chassis属性获取/设置我们的实例,进一步迈出了这一步。
詹姆斯

0

试试这个:

class Chassis {}
class RacingChassis : Chassis {}
class SuperChassis : RacingChassis {}

class Car {
    private var chassis: Chassis? = nil
    func getChassis() -> Chassis? {
        return chassis
    }

    func setChassis(chassis: Chassis) {
        self.chassis = chassis
    }
}

class RaceCar: Car {
    private var chassis: RacingChassis {
        get {
            return getChassis() as! RacingChassis
        }
        set {
            setChassis(chassis: newValue)
        }
    }

    override init() {
        super.init()

        chassis = RacingChassis()
    }
}

class SuperCar: RaceCar {
    private var chassis: SuperChassis {
        get {
            return getChassis() as! SuperChassis
        }
        set {
            setChassis(chassis: newValue)
        }
    }

    override init() {
        super.init()

        chassis = SuperChassis()
    }
}

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 Car {
    var chassis:Chassis?

    func inspect() {
        chassis?.checkForRust()
    }
}

class RaceCar: Car {
    var racingChassis: RacingChassis? {
        get {
            return chassis as? RacingChassis
        } 
    }

    override func inspect() {
        super.inspect()
        racingChassis?.tuneSuspension()
    }
}

0

其他答案略有不同,但更简单,更安全,并且有一些不错的好处。

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    var racingChassis: RacingChassis? {
        get {
            return chassis as? RacingChassis
        }
    }
}

好处包括对必须采用何种底盘(var,let,optional等)没有任何限制,并且很容易将RaceCar子类化。然后,RaceCar的子类可以具有自己的底盘(或RacingChassis)计算值。


-2

只需使用不同的命名约定(例如imgview)设置新的imageview属性,因为imageView已经是其自己的属性,因此我们无法分配2个强属性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.