inheritance

A class can inherit methods, attributes, and other features from another class. When a class inherits from another class, the inherited class is called a subclass, and the inherited class is called a superclass (or superclass). In Swift, inheritance is a fundamental feature that distinguishes “classes” from other types.

In Swift, classes can call and access superclass methods, properties, and subscripts, and override them to optimize or modify their behavior. Swift checks that your override definition has a matching definition in the superclass to ensure that your override behavior is correct.

Attribute observers can be added to inherited attributes in a class so that the class is notified when the attribute value changes. You can add a property observer for any property, whether it was originally defined as a storage or computational property.


Define a base class

Classes that do not inherit from other classes are called base classes.

Pay attention to

Classes in Swift do not inherit from a generic base class. If you don’t specify a superclass for your class, it automatically becomes a base class.

The following example defines a base class called Vehicle. This base class declares a stored property named currentSpeed with a default value of 0.0 (the property type is inferred to be Double). The value of the currentSpeed property is used by a String read-only computational property called Description to create a description of the vehicle.

The Vehicle base class also defines a method called makeNoise. This method doesn’t actually do anything for the Vehicle instance, but will then be customized by subclasses of Vehicle:

class Vehicle {
    var currentSpeed = 0.0
    var description: String {
        return "traveling at \(currentSpeed) miles per hour"
    }
    func makeNoise(a) {
        // Do nothing -- because traffic is not necessarily noisy}}Copy the code

A new instance of Vehicle can be created using the initialization syntax, that is, the class name followed by an empty parenthesis:

let someVehicle = Vehicle(a)Copy the code

Now that a new instance of Vehicle has been created, you can access its description property to print the current Vehicle speed:

print("Vehicle: \(someVehicle.description)")
// Print "Vehicle: Traveling at 0.0 miles per hour"
Copy the code

The Vehicle class defines a Vehicle class that has common features, but is virtually useless on its own. To make it more useful, it needs to be further refined to be able to describe a specific type of vehicle.


Subclasses to generate

Subclass generation refers to the creation of a new class from an existing one. Subclasses inherit the features of superclasses and can be further refined. You can also add new features to subclasses. To indicate the superclass of a class, write the superclass name after the subclass name, separated by a colon:

class SomeClass: SomeSuperclass {
    // Here is the definition of a subclass
}
Copy the code

The next example defines a subclass called Bicycle, which inherits from the parent Vehicle:

class Bicycle: Vehicle {
    var hasBasket = false
}
Copy the code

The new Bicycle class automatically inherits all features of the Vehicle class, such as the currentSpeed and Description attributes, as well as the makeNoise() method.

In addition to the inherited properties, Bicycle defines a storage property hasBasket (inferred as Bool) with a default value of false.

By default, all new Bicycle instances you create do not have a basket (that is, the hasBasket attribute defaults to false). After creating this instance, you can set hasBasket to true for Bicycle:

let bicycle = Bicycle()
bicycle.hasBasket = true
Copy the code

You can also change the currentSpeed property inherited from Bicycle and the Description property inherited from query:

bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
Traveling at 15.0 miles per hour Bicycle: traveling at 15.0 miles per hour
Copy the code

Subclasses can continue to be inherited by other classes. The following example creates a subclass for Bicycle called Tandem:

class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}
Copy the code

Tandem inherits all attributes and methods from Bicycle, which in turn inherits all attributes and methods from Vehicle. Tandem also added a new storage type called currentNumberOfPassengers attribute, the default value is 0.

If you create an instance of Tandem, you can use all of its new properties and inherited properties, as well as query the read-only property description inherited from Vehicle:

let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// Print: "Tandem: Traveling at 22.0 miles per hour"
Copy the code

rewrite

Subclasses can provide their own custom implementations for inherited instance methods, class methods, instance properties, class properties, or subscripts. We call this behavior rewriting.

If you want to override a feature, you need to prefix the override keyword before the override definition. By doing so, you are indicating that you want to provide a rewrite, rather than mistakenly providing the same definition. Unexpected overrides can result in unexpected errors, and any overrides that lack the override keyword are considered errors at compile time.

The override keyword alerts the Swift compiler to check if the class’s superclass (or one of its superclasses) has a declaration matching the overwritten version. This check ensures that your rewrite definition is correct.

Access methods, attributes, and subscripts of the superclass

When you override superclass methods, attributes, or subscripts in a subclass, it sometimes helps to use an existing superclass implementation in your override version. For example, you can refine the behavior of an existing implementation, or store a modified value in an inherited variable.

Where appropriate, you can access the superclass version’s methods, attributes, or subscripts by using the super prefix: in a rewrite implementation of the method someMethod(), the superclass version’s someMethod() method can be called via super.somemethod (). In a rewrite implementation of the getter or setter for property someProperty, the superclass version of the someProperty property can be accessed via super.someProperty. In the overriding implementation of the subscript, the same subscript in the superclass version can be accessed via super[someIndex].

Overriding methods

In a subclass, you can override an inherited instance method or class method to provide a custom or alternative method implementation.

The following example defines a new subclass of Vehicle called Train, which overrides the makeNoise() method inherited from the Vehicle class:

class Train: Vehicle {
    override func makeNoise(a) {
        print("Choo Choo")}}Copy the code

If you create a new instance of Train and call its makeNoise() method, you’ll find that the Train version of the method is called:

let train = Train()
train.makeNoise()
// Print "Choo Choo"
Copy the code
Rewrite attributes

You can override inherited instance or type properties, provide your own custom getters and setters, or add property observers so that the overridden properties can see when the underlying property values change.

Overridden attributeGettersSetters

You can provide custom getters (or setters) to override any inherited property, whether stored or computed. A subclass does not know whether the inherited property is stored or computed, it only knows that the inherited property will have a name and type. When you override an attribute, you must specify its name and type. This allows the compiler to check that the property you’re overwriting matches a property of the same type and name in the superclass.

You can rewrite an inherited read-only property as a read-write property by providing getters and setters in the overridden version of the property. However, you cannot override an inherited read-write property as a read-only property.

Pay attention to

If you provide a setter in the override property, you must also provide a getter. If you don’t want to modify the inherited property value in the getter of the overridden version, you can return the inherited value directly via super.someProperty, where someProperty is the name of the property you want to override.

The following example defines a new class called Car, which is a subclass of Vehicle. This class introduces a new stored property called Gear with a default value of integer 1. The Car class overrides the description attribute inherited from Vehicle to provide a custom description containing the current tap:

class Car: Vehicle {
    var gear = 1
    override var description: String {
        return super.description + " in gear \(gear)"}}Copy the code

The overridden description attribute first calls super.description to return the description attribute of the Vehicle class. Later, the Car version of description adds some extra text at the end to provide information about the current tap.

If you create an instance of Car and set its Gear and currentSpeed properties, you can see that its description returns the custom description in Car:

let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
Traveling at 25.0 miles per hour in gear 3

Copy the code
Overrides the property viewer

You can add a property observer to an inherited property by overriding the property. This way, regardless of how the inherited property was originally implemented, you will be notified when its value changes. For more on attribute viewer, see Attribute Viewer.

Pay attention to

You cannot add property observers to inherited constant-store properties or inherited read-only computation-only properties. The values of these properties cannot be set, so it is not appropriate to provide willSet or didSet implementations for them.

Also note that you cannot provide overridden setters and overridden property observers at the same time. If you want to watch the value of a property change, and you’ve provided a custom setter for that property, then you can watch any value change in the setter.

The following example defines a new class called AutomaticCar, which is a subclass of Car. 13. AutomaticCar denotes a car with an automatic gear that automatically selects the right gear based on its current speed:

class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1}}}Copy the code

When you set the currentSpeed property of AutomaticCar, the property’s didSet observer automatically sets the Gear property to select an appropriate gear for the new speed. Specifically, the attribute viewer divides the new speed value by 10, then goes down to the nearest integer value, and finally adds one to get the gear value. For example, at speed 35.0, gear 4:

let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
Traveling at 35.0 miles per hour in Gear 4 AutomaticCar: Traveling at 35.0 miles per hour in Gear 4
Copy the code

To prevent the rewrite

You can prevent methods, properties, or subscripts from being overwritten by marking them final by prefixing the declaration keyword with final modifiers (such as Final var, Final Func, Final Class func, and Final subscript).

Any code that attempts to rewrite a method, property, or subscript with a final tag will report an error at compile time. Methods, attributes, or subscripts ina class extension can also be marked final in the extension definition. You can mark an entire class as final by adding the final class modifier before the keyword class. Such a class is not inheritable, and attempting to inherit such a class will result in a compilation error.