147

In Swift, can someone explain how to override a property on a superclass's with another object subclassed from the original property?

Take this simple example:

class Chassis {}
class RacingChassis : Chassis {}

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

This gives the error:

Cannot override with a stored property 'chassis'

If I have chassis as 'var' instead, I get the error:

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

The only thing I could find in the guide under "Overriding Properties" indicates that we have to override the getter and setter, which may work for changing the value of the property (if it's 'var'), but what about changing the property class?

James
  • 1,581
  • 2
  • 10
  • 5

15 Answers15

127

Swift does not allow you to change the class type of any variables or properties. Instead you can create an extra variable in the subclass that handles the new class type:

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")
            }
        }
    }
}

It seems one cannot declare a property with the let syntax and override it with var in it’s subclass or vice-versa, which may be because the superclass implementation might not be expecting that property to change once initialized. So in this case the property needs to be declared with ‘var’ in the superclass as well to match the subclass (as shown in the snippet above). If one cannot change the source code in the superclass then its probably best to destroy the current RaceCar and create a new RaceCar every time the chassis needs to be mutated.

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Dash
  • 17,188
  • 6
  • 48
  • 49
  • Did not think the `override` keyword would apply to properties too, cool! – Erik Jun 07 '14 at 06:20
  • I updated the superclass to have an immutable property but kept everything else the same and it seems to work fine. – Dash Jun 07 '14 at 07:00
  • 1
    Sorry just deleted my comment and then saw your reply. I was having a problem because in my real case my superclass is an Objective-C class with a `strong` property and I was getting an error trying to override it - but it seems I missed that it translates to an "implicitly unwrapped optional" (`chassis!`) in Swift, so `override var chassis : Chassis!` fixes it. – James Jun 07 '14 at 07:11
  • 4
    This no longer works with Swift 1.1 under Xcode 6.1. Generates error: "Cannot override immutable 'let' property 'chassis' with the getter of a 'var'". Any ideas for better solution? – Darrarski Oct 20 '14 at 10:32
  • 1
    Also no longer works in Swift 1.2 under Xcode 6.3 beta. Cannot override mutable property 'A' of type 'Type1' with covariant type 'Type2' – bubuxu Feb 14 '15 at 05:20
  • 22
    This is really lame, and a failing of Swift, imo. Since RacingChassis *is* a Chassis, the compiler should have no problem allowing you to refine the class of the property in a subclass. Lots of languages allow this, and not supporting it leads to ugly workarounds like this. No offense. :/ – devios1 Feb 04 '16 at 00:00
  • 1
    Optionals provide a means of suggesting that a variable may not be present because it was provided for by a function that no longer returns the expected value. Such cases may happen if the function was overridden. Take a look at my answer for details. –  Sep 30 '16 at 06:22
  • I wish something like this was possible using extensions and protocols – user134611 Jul 09 '18 at 05:26
  • @devios1 I agree, Swift 5.5 just came out and we have async-await now but it sucks that we don't have this basic functionality – Saad Rehman Jun 08 '21 at 02:52
12

This seems to work

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()
David Arve
  • 804
  • 1
  • 7
  • 10
  • 1
    This works as the chassis property is defined as `let` in the `Car` class which makes it impossible to change. We can only change the `chassis` property on the `RaceCar` class. – David Arve Jun 12 '14 at 13:18
  • 5
    This doesn't work for Swift 2.0. It gives "error: cannot override immutable 'let' property 'chassis' with the getter of a 'var'" – mohamede1945 Dec 09 '15 at 08:41
  • This does not work in swift 4.0 too. layground execution failed: error: MyPlaygrounds.playground:14:15: error: cannot override immutable 'let' property 'chassis' with the getter of a 'var' override var chassis: RacingChassis { ^ MyPlaygrounds.playground:11:6: note: attempt to override property here let chassis = Chassis() ^ – karim May 09 '18 at 07:43
11

Try this:

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
     } 
}

Then:

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

Detail in http://www.mylonly.com/14957025459875.html

adimtxg0422
  • 111
  • 1
  • 2
2

Theoretically, you're allowed to do it this way...

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! }
}

This works perfectly in an Xcode playground.

But, if you try this in a real project, a compiler error tells you:

Declaration 'view' cannot override more than one superclass declaration

I've only checked Xcode 6.0 GM as of now.

Unfortunately, you'll have to wait until Apple fixes this.

I've submitted a bug report too. 18518795

aleclarson
  • 18,087
  • 14
  • 64
  • 91
2

The Solution Dash provided works well except that the super class has to be declared with the let keyword rather than var. Here is a solution that is possible but NOT RECOMENDED!

The solution below will compile with Xcode 6.2, SWIFT 1.1 (if all the classes are in different swift files) but should be avoided because IT CAN LEAD TO UNEXPECTED BEHAVIORS(INCLUDING A CRASH, especially when using non-optional types). NOTE: THIS DOES NOT WORK WITH 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
        }
    }
}
Lukman
  • 33
  • 4
  • 1
    It doesn't work for Swift 2.0. It gives "cannot override mutable property 'chassis' of type 'Chassis?' with covariant type 'RacingChassis?'" – mohamede1945 Dec 09 '15 at 08:43
2

You can achieve it with use of generics:

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

With this approach you will have 100% type safe code without force unwraps.

But that this is kind of a hack, and if you will need to redefine several properties than you class declarations will look like a total mess. So be careful with this approach.

Al Zonke
  • 532
  • 6
  • 8
1

I've seen a lot of reasons why designing an API using variables instead of functions is problematic and to me using computed properties feels like a workaround. There are good reasons to keep your instance variables encapsulated. Here I've created a protocol Automobile that Car conforms to. This protocol has an accessor method that returns a Chassis object. Since Car conforms to it the RaceCar subclass can override it and return a different Chassis subclass. This allows the Car class to program to an interface (Automobile) and the RaceCar class that knows about RacingChassis can access the _racingChassis variable directly.

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
    }
}

Another example of why designing an API using variables breaks down is when you have variables in a protocol. If you'd like to break out all of the protocol functions into an extensions you can, except stored properties cannot be placed in extensions and have to be defined in the class (to get this to compile you'd have to uncomment the code in AdaptableViewController class and remove the mode variable from the extension):

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
    }
}

The above code will have this compiler error: "Extensions may not have stored properties". Here's how you can re-write the example above so that everything in the protocol can be separated out in the extension by using functions instead:

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

class AdaptableViewController: UIViewController {
}

extension AdaptableViewController: Adaptable {
    func mode() -> Int {
        return 0
    }
    func adapt() {
        // adapt code
    }
}
Korey Hinton
  • 2,532
  • 2
  • 25
  • 24
1

Depending on how you plan on using the property, the simplest way to do this is to use an optional type for your subclass, and override the didSet {} method for the 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
        }
    }
}

Obviously you need to spend some time checking to ensure the classes can be initialized this way, but by setting the properties to optional, you protect yourself against situations where casting no longer works.

brandonscript
  • 68,675
  • 32
  • 163
  • 220
0

Try this:

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()
    }
}
Fried Rice
  • 3,295
  • 3
  • 18
  • 27
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())
    }
}
Dombi Bence
  • 746
  • 7
  • 23
0

The following allows a single object to be used in base and derived classes. In the derived class, use the derived object property.

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()
    }
}
Stickley
  • 4,561
  • 3
  • 30
  • 29
0

A slight variation of other answers, but simpler and safer with some nice benefits.

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    let chassis: Chassis

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

The benefits include that there is no restriction on what chassis must be (var, let, optional, etc), and it is easy to subclass RaceCar. Subclasses of RaceCar can then have their own computed value for chassis (or racingChassis).

Ryan H
  • 1,656
  • 14
  • 15
  • 2
    The problem with this answer is a raceCar has 2 chassis! raceCar.chassis and raceCar.racingChassis – malhal Dec 30 '20 at 12:52
  • I've modified my answer to be a little more realistic by providing an initializer to specify the chassis, so that the Car could be initialized with a RacingChassis. Hopefully it's more clear that there is in fact only one chassis, but it can be referenced either as raceCar.chassis or as raceCar.raceChassis (the latter in the scenario where you've got a raceCar and you know you want the RacingChassis). Obviously there is the loophole where you could have a RaceCar without a RacingChassis, but it's at least handled elegantly with optionals. – Ryan H Dec 30 '20 at 19:36
0

Simple using generics, e.g.

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

Also, Chassis don't even need to be subclasses:

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

class Car<ChassisType : Chassis> {
    var chassis = ChassisType()
}
malhal
  • 26,330
  • 7
  • 115
  • 133
-1

You can simply create another variable of RacingChassis.

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

class RaceCar: Car {
let raceChassis: RacingChassis
init(){
        raceChassis = RacingChassis()
}}
Anton
  • 559
  • 2
  • 15
  • This works, but I think Dash's answer takes this one step further by overriding `chassis` and allowing us to get/set our `RacingChassis` instance with the `chassis` property. – James Jun 07 '14 at 06:45
-2

just set new imageview property with different naming convention like imgview because imageView is already its own property and we cannot assign 2 strong properties.