はじめに
公式ページのSwift 言語ガイド「The Swift Programming Language Swift 5.3」に基づき記載いたします。本ページでは、「継承」について記載いたします。
記載内容に誤り等ございましたら、ご連絡をいただければ幸いです。
13. 継承
クラスは、メソッド、プロパティ、およびその他の特性を別のクラスから継承できます。あるクラスが別のクラスから継承する場合、継承するクラスはサブクラスと呼ばれ、継承元のクラスはスーパークラスと呼ばれます。継承は、Swiftの他の型からクラスを区別する基本的な動作です。
Swiftのクラスは、スーパークラスに属するメソッド、プロパティ、およびサブスクリプトを呼び出してアクセスし、それらのメソッド、プロパティ、およびサブスクリプトの独自のオーバーライドバージョンを提供して、動作を改良または変更できます。Swiftは、オーバーライド定義がスーパークラス定義に一致することを確認することにより、オーバーライドが正しいことを確認するのに役立ちます。
クラスは、プロパティの値が変更されたときに通知を受けるために、継承されたプロパティにプロパティオブザーバーを追加することもできます。プロパティオブザーバーは、元々格納型プロパティとして定義されているか計算型プロパティとして定義されているかに関係なく、任意のプロパティに追加できます。
13.1. 基本クラスの定義
別のクラスから継承しないクラスは、基本クラスと呼ばれます。
注意
Swiftのクラスは、ユニバーサル基本クラスから継承しません。スーパークラスを指定せずに定義したクラスは、自動的に基礎となる基本クラスになります。
以下の例では、Vehicleという基本クラスを定義しています。この基本クラスは、currentSpeedと呼ばれる格納型プロパティを定義します。デフォルト値は0.0です。(プロパティ型はDoubleと推測されます)currentSpeedプロパティの値は、descriptionと呼ばれる読み取り専用のString型の計算型プロパティによって使用され、車両の説明を作成します。
Vehicle基本クラスは、makeNoiseと呼ばれるメソッドも定義します。このメソッドは、基本のVehicleインスタンスに対して実際には何もしませんが、後でVehicleのサブクラスによってカスタマイズされます。
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// 何もしない - 任意の車両が必ずしも音を立てるわけではない
}
}
初期化構文を使用してVehicleの新しいインスタンスを作成します。これは、型名の後に空の括弧が続くものとして記述されます。
let someVehicle = Vehicle()
新しいVehicleインスタンスを作成したら、そのdescriptionプロパティにアクセスして、車両の現在の速度に関する人間が読める形式の説明を出力できます。
print("Vehicle: \(someVehicle.description)")
// Vehicle: traveling at 0.0 miles per hour
Vehicleクラスは、任意の車両に共通の特性を定義しますが、それ自体ではあまり使用されません。より便利にするには、より具体的な種類の車両を記述するように改良する必要があります。
13.2. サブクラス化
サブクラス化は、既存のクラスに基づいて新しいクラスを作成する行為です。サブクラスは既存のクラスから特性を継承し、それを改良することができます。サブクラスに新しい特性を追加することもできます。
サブクラスにスーパークラスがあることを示すには、スーパークラス名の前にコロンで区切ってサブクラス名を記述します。
class SomeSubclass: SomeSuperclass {
// サブクラスの定義はここにあります
}
次の例では、Vehicleのスーパークラスを持つBicycleというサブクラスを定義しています。
class Bicycle: Vehicle {
var hasBasket = false
}
新しいBicycleクラスは、currentSpeedプロパティとdescriptionプロパティ、makeNoise()メソッドなど、Vehicleのすべての特性を自動的に取得します。
継承する特性に加えて、Bicycleクラスは新しい格納型プロパティhasBasketを定義し、デフォルト値はfalseです(プロパティをBool型と推測する)。
デフォルトでは、作成する新しいBicycleインスタンスにはバスケットがありません。特定のBicycleインスタンスが作成された後、そのインスタンスに対してhasBasketプロパティをtrueに設定できます。
let bicycle = Bicycle()
bicycle.hasBasket = true
Bicycleインスタンスの継承されたcurrentSpeedプロパティを変更し、インスタンスの継承されたdescriptionプロパティをクエリすることもできます。
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour
サブクラス自体をサブクラス化できます。次の例では、「Tandem」と呼ばれる2人乗り自転車のサブクラスを作成します。
class Tandem: Bicycle {
var currentNumberOfPassengers = 0
}
TandemはBicycleからすべてのプロパティとメソッドを継承し、BicycleはVehicleからすべてのプロパティとメソッドを継承します。Tandemサブクラスは、currentNumberOfPassengersと呼ばれる新しい格納型プロパティも追加します。デフォルト値は0です。
Tandemのインスタンスを作成する場合、その新しいプロパティと継承されたプロパティのいずれかを操作して、Vehicleから継承する読み取り専用のdescriptionプロパティをクエリできます。
let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// Tandem: traveling at 22.0 miles per hour
13.4. オーバーライド
サブクラスは、インスタンスメソッド、型メソッド、インスタンスプロパティ、型プロパティ、またはスーパークラスから継承するサブスクリプトの独自のカスタム実装を提供できます。これはオーバーライドとして知られています。
継承される特性をオーバーライドするには、オーバーライドする定義の前にoverrideキーワードを付けます。そうすることで、オーバーライドを提供するつもりであり、誤って一致する定義を提供していないことが明確になります。誤ってオーバーライドすると、予期しない動作が発生する可能性があり、overrideキーワードを使用しないオーバーライドは、コードのコンパイル時にエラーと診断されます。
また、overrideキーワードは、オーバーライドするクラスのスーパークラス(またはその親の1つ)に、オーバーライド用に指定した宣言と一致する宣言があることを確認するようにSwiftコンパイラに要求します。このチェックにより、オーバーライドする定義が正しいことが確認されます。
13.5. スーパークラスのメソッド、プロパティおよびサブスクリプトへのアクセス
サブクラスにメソッド、プロパティ、またはサブスクリプトのオーバーライドを指定する場合、オーバーライドの一部として既存のスーパークラスの実装を使用すると便利な場合があります。たとえば、その既存の実装の動作を改良したり、変更された値を既存の継承された変数に格納したりできます。
これが適切な場合は、スーパープレフィックスを使用して、メソッド、プロパティ、またはサブスクリプトのスーパークラスバージョンにアクセスします。
- someMethod()という名前のオーバーライドされたメソッドは、オーバーライドするメソッド実装内でsuper.someMethod()を呼び出すことにより、someMethod()のスーパークラスバージョンを呼び出すことができます。
- somePropertyと呼ばれるオーバーライドされたプロパティは、オーバーライドするゲッターまたはセッター実装内でsuper.somePropertyとしてsomePropertyのスーパークラスバージョンにアクセスできます。
- someIndexのオーバーライドされたサブスクリプトは、オーバーライドされたサブスクリプト実装内から、super[someIndex]と同じサブスクリプトのスーパークラスバージョンにアクセスできます。
13.5. メソッドのオーバーライド
継承されたインスタンスまたは型メソッドをオーバーライドして、サブクラス内のメソッドのカスタマイズされた実装または代替実装を提供できます。
次の例では、Trainと呼ばれるVehicleの新しいサブクラスを定義します。これは、TrainがVehicleから継承するmakeNoise()メソッドをオーバーライドします。
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}
Trainの新しいインスタンスを作成し、そのmakeNoise()メソッドを呼び出すと、メソッドのTrainサブクラスバージョンが呼び出されていることがわかります。
let train = Train()
train.makeNoise()
// "Choo Choo"を出力します
13.6. プロパティのオーバーライド
継承されたインスタンスまたは型プロパティをオーバーライドして、そのプロパティに独自のカスタムゲッターとセッターを提供したり、プロパティオブザーバーを追加して、基になるプロパティ値が変更されたときにオーバーライドするプロパティが監視できるようにすることができます。
13.7. プロパティゲッターとセッターのオーバーライド
継承されたプロパティが継承元で格納型プロパティとして実装されているか計算型プロパティとして実装されているかに関係なく、継承されたプロパティをオーバーライドするカスタムゲッター(および適切な場合はセッター)を提供できます。継承されたプロパティの格納型または計算型の性質は、サブクラスによって認識されません。継承されたプロパティが特定の名前と型を持っていることだけを認識します。コンパイラがオーバーライドが同じ名前と型のスーパークラスプロパティと一致することを確認できるようにするには、オーバーライドするプロパティの名前と型の両方を常に指定する必要があります。
サブクラスプロパティオーバーライドでゲッターとセッターの両方を提供することにより、継承された読み取り専用プロパティを読み取り/書き込みプロパティとして提示できます。ただし、継承された読み取り/書き込みプロパティを読み取り専用プロパティとして提示することはできません。
注意
プロパティオーバーライドの一部としてセッターを提供する場合は、そのオーバーライドのゲッターも提供する必要があります。オーバーライドするゲッター内で継承されたプロパティの値を変更したくない場合は、ゲッターからsuper.somePropertyを返すことで、継承された値を渡すことができます。somePropertyは、オーバーライドするプロパティの名前です。
次の例では、VehicleのサブクラスであるCarという新しいクラスを定義します。Carクラスは、デフォルトの整数値1のgearと呼ばれる新しい格納型プロパティを導入します。Carクラスは、Vehicleから継承するdescriptionプロパティもオーバーライドして、現在のギアを含むカスタムdescriptionを提供します。
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}
descriptionプロパティのオーバーライドは、Vehicleクラスのdescriptionプロパティを返すsuper.descriptionを呼び出すことから始まります。次に、Carクラスのdescriptionのバージョンは、現在のギアに関する情報を提供するために、この説明の最後にいくつかのテキストを追加します。
Carクラスのインスタンスを作成し、そのgearプロパティとcurrentSpeedプロパティを設定すると、そのdescriptionプロパティがCarクラス内で定義されたカスタマイズされたdescriptionを返すことがわかります。
let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// Car: traveling at 25.0 miles per hour in gear 3
13.7. プロパティオブザーバーのオーバーライド
プロパティのオーバーライドを使用して、継承されたプロパティにプロパティオブザーバーを追加できます。これにより、そのプロパティが最初にどのように実装されたかに関係なく、継承されたプロパティの値が変更されたときに通知を受けることができます。プロパティオブザーバーの詳細については、プロパティオブザーバーを参照してください。
注意
継承された定数格納型プロパティまたは継承された読み取り専用の計算型プロパティにプロパティオブザーバーを追加することはできません。これらのプロパティの値は設定できないため、オーバーライドの一部としてwillSetまたはdidSetの実装を提供することは適切ではありません。
同じプロパティに対して、オーバーライドするセッターとオーバーライドするプロパティオブザーバーの両方を提供することはできないことにも注意してください。プロパティの値の変更を監視する必要があり、そのプロパティのカスタムセッターを既に提供している場合は、カスタムセッター内から値の変更を監視するだけです。
次の例では、CarのサブクラスであるAutomaticCarと呼ばれる新しいクラスを定義します。AutomaticCarクラスは、現在の速度に基づいて使用する適切なギアを自動的に選択する自動ギアボックスを備えた車を表します。
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
AutomaticCarインスタンスのcurrentSpeedプロパティを設定するたびに、プロパティのdidSetオブザーバーは、インスタンスの歯車プロパティを新しい速度に適した歯車の選択肢に設定します。具体的には、プロパティオブザーバーは、新しいcurrentSpeed値を10で割り、最も近い整数に切り捨てられたものに1を足した歯車を選択します。速度が35.0の場合、歯車は4になります。
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4
13.8. オーバーライドの防止
メソッド、プロパティ、またはサブスクリプトがfinalとしてマークされることにより、オーバーライドされないようにすることができます。これを行うには、メソッド、プロパティ、またはサブスクリプトのイントロデューサキーワード(final var、final func、final class func、final subscriptなどのように)の前にfinal修飾子を記述します。
サブクラスのfinalメソッド、プロパティ、またはサブスクリプトをオーバーライドしようとすると、コンパイル時エラーとして報告されます。拡張機能のクラスに追加するメソッド、プロパティ、またはサブスクリプトは、拡張機能の定義内でfinalとしてマークすることもできます。
クラス定義(finalクラス)のclassキーワードの前にfinal修飾子を記述することにより、クラス全体をfinalとしてマークできます。最終クラスをサブクラス化しようとすると、コンパイル時エラーとして報告されます。