読み込み中...

Swiftでマスターする継承のやり方10選

Swiftのロゴと継承のシンボルが描かれた画像 Swift
この記事は約19分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

皆さん、Swiftのプログラミング言語に興味を持っていますか?

特に、Swiftでの「継承」というキーワードに興味を感じている方も多いのではないでしょうか。

この記事を読めば、Swiftでの継承をマスターすることができるようになります。

これから、Swiftにおける継承の基本から応用までを、具体的なサンプルコードと共にわかりやすく解説していきます。

●Swiftとは

SwiftはAppleが開発したプログラミング言語で、iOS、macOS、watchOS、tvOSなどのAppleのプラットフォーム向けのアプリを開発する際に使われます。

その特徴として、高速性、安全性、そして直感的なコーディングが可能な点が挙げられます。

○Swiftの歴史と特徴

Swiftは2014年にAppleが公開した比較的新しい言語です。

それまでのObjective-Cという言語の後継として開発され、多くの開発者に迅速に受け入れられました。

その理由は、Swiftが提供するシンプルで直感的な文法や、安全性を重視した設計にあります。

また、Playgroundという機能を用いて、コードを書きながら即座に結果を確認できるのも、初学者には非常に魅力的です。

○継承を理解するための基本

継承は、オブジェクト指向プログラミングにおける主要な概念の1つです。

オブジェクト指向には「カプセル化」「ポリモーフィズム」「継承」などの3つの主要な柱があり、この中でも継承は新しいクラスを作成する際に、既存のクラスの属性や振る舞いを引き継ぐことを可能にする機能です。

これにより、コードの再利用性が向上し、効率的にプログラムを構築することができます。

Swiftでは、継承を利用することで一つの基本クラスから派生クラスを作成することができ、これによりコードの簡潔化や効率的な開発が実現します。

しかし、継承を適切に使用するためには、その概念や仕組みをしっかりと理解する必要があります。

●継承の基本概念

継承はオブジェクト指向プログラミングの中心的な概念の一つです。

Swiftにおいても、この概念は非常に重要で、より効率的かつ効果的なコードを書くための鍵となる部分です。

○継承とは何か?

継承とは、あるクラス(親クラスやスーパークラスとも呼ばれる)の属性や振る舞いを引き継いで、新しいクラス(子クラスやサブクラスとも呼ばれる)を作成することを指します。

この新しいクラスは、親クラスの全ての属性や振る舞いを継承することができ、さらに新しい属性や振る舞いを追加することも可能です。

具体的には、親クラスに定義されたプロパティやメソッドを、子クラスでもそのまま利用することができます。

また、子クラスで同じ名前のメソッドを再定義することで、親クラスの振る舞いを上書き(オーバーライド)することも可能です。

○継承のメリットと使いどころ

継承の最大のメリットは、コードの再利用性が向上することです。

共通の振る舞いや属性を親クラスにまとめて定義しておけば、その親クラスを継承するすべての子クラスでその振る舞いや属性を利用することができるため、同じコードを何度も書く必要がありません。

また、継承はソフトウェアの拡張性を向上させる助けとなります。

親クラスのみを更新することで、その親クラスを継承するすべての子クラスに更新が反映されるため、メンテナンスが効率的に行えます。

しかし、継承を行う際には慎重に設計を行う必要があります。

無闇に継承を多用すると、クラスの関係が複雑になり、コードの可読性やメンテナンスが困難になる可能性があります。

継承を利用する際には、本当に継承が必要かどうかをよく考え、適切に使用することが求められます。

●Swiftにおける継承の使い方

Swift言語は継承の概念を取り入れており、これにより新しいクラスを作成する際に既存のクラスの属性やメソッドを引き継ぐことが可能です。

Swiftでの継承の使用方法を理解することは、効果的なアプリケーション開発において欠かせないスキルとなっています。

○サンプルコード1:基本的なクラスの継承

Swiftでクラスの継承を行う場合、親クラスの後ろにコロンを付け、その後に子クラスの名前を記述します。

ここでは、継承の基本的な使い方を表すサンプルコードを紹介します。

class Vehicle {
    var speed: Int = 0
    func drive() {
        print("走行中: \(speed)km/h")
    }
}

class Car: Vehicle {
    var numberOfDoors = 4
}

let myCar = Car()
myCar.drive() // 走行中: 0km/h
print(myCar.numberOfDoors) // 4

このコードでは、Vehicleクラスを基にしてCarクラスを作成しています。

CarクラスはVehicleクラスのすべてのプロパティとメソッドを継承しているため、driveメソッドをそのまま使用することができます。

○サンプルコード2:プロパティのオーバーライド

Swiftでは、子クラスで親クラスのプロパティをオーバーライドすることが可能です。

オーバーライドする際は、overrideキーワードを使用します。

class Vehicle {
    var speed: Int = 0
    var description: String {
        return "走行速度: \(speed)km/h"
    }
}

class RaceCar: Vehicle {
    override var speed: Int {
        didSet {
            if speed > 200 {
                speed = 200
            }
        }
    }
}

let formulaOne = RaceCar()
formulaOne.speed = 250
print(formulaOne.description) // 走行速度: 200km/h

このコードでは、RaceCarクラスでspeedプロパティをオーバーライドしています。

speedが200を超える場合、200に制限するように設定しています。

このようにオーバーライドを活用することで、親クラスの振る舞いをカスタマイズすることができます。

○サンプルコード3:メソッドのオーバーライド

Swiftでは、継承されたメソッドを子クラスで再定義することが可能です。

このように子クラスで変更を加えることを「メソッドのオーバーライド」と呼びます。

メソッドのオーバーライドを行う場合も、overrideキーワードを使用する必要があります。

例として、動物を表すクラスと、そのサブクラスである犬を表すクラスのサンプルコードを紹介します。

class Animal {
    func sound() {
        print("動物の音")
    }
}

class Dog: Animal {
    override func sound() {
        print("ワンワン")
    }
}

let dog = Dog()
dog.sound()  // ワンワン

このコードでは、Animalクラスにsoundというメソッドを定義しています。

Dogクラスでは、Animalクラスから継承したsoundメソッドをオーバーライドして、犬特有の鳴き声「ワンワン」と表示するように変更しています。

○サンプルコード4:イニシャライザのオーバーライド

Swiftでは、子クラスで親クラスのイニシャライザをオーバーライドすることもできます。

イニシャライザのオーバーライドもメソッドと同様に、overrideキーワードを用いて行います。

ここでは、書籍としての基本的な情報を持つBookクラスと、そのサブクラスであるComicBookクラスのサンプルコードを紹介します。

class Book {
    var title: String

    init(title: String) {
        self.title = title
    }
}

class ComicBook: Book {
    var author: String

    init(title: String, author: String) {
        self.author = author
        super.init(title: title)
    }
}

let comic = ComicBook(title: "ドラゴンボール", author: "鳥山 明")
print(comic.title)  // ドラゴンボール
print(comic.author)  // 鳥山 明

このコードでは、Bookクラスにタイトルを指定するイニシャライザが定義されています。

ComicBookクラスでは、新たに作者の情報を追加しており、親クラスのイニシャライザをオーバーライドしています。

○サンプルコード5:継承を使ったポリモーフィズム

Swiftの継承は、ポリモーフィズムというオブジェクト指向プログラミングの特性を活用するための重要な機能の一つです。

ポリモーフィズムとは、異なるクラスのオブジェクトを同一のインターフェースで扱うことができる性質を指します。

ここでは、AnimalクラスとそのサブクラスであるDogクラス、Catクラスを使用して、ポリモーフィズムを表すサンプルコードを紹介します。

class Animal {
    func sound() {
        print("動物の音")
    }
}

class Dog: Animal {
    override func sound() {
        print("ワンワン")
    }
}

class Cat: Animal {
    override func sound() {
        print("ニャー")
    }
}

let animals: [Animal] = [Dog(), Cat()]

for animal in animals {
    animal.sound()  
}
// ワンワン
// ニャー

このコードでは、異なるクラスのオブジェクト(DogとCat)が、同じインターフェース(soundメソッド)を通じて扱われています。

これはポリモーフィズムの典型的な例となっています。

●継承の応用例

Swiftの継承機能は基本的な使い方だけでなく、さまざまな応用例も持っています。

これにより、もっと柔軟なプログラミングが可能となります。

今回は継承の応用的な使い方の中からいくつかピックアップし、詳しく解説していきます。

○サンプルコード6:複数のクラスを組み合わせる

継承を利用すると、複数のクラスの特性を組み合わせて新しいクラスを作成することができます。

下記のサンプルコードでは、動物のクラスと飛べる特性を持つクラスを組み合わせて、飛べる犬のクラスを作成しています。

class Animal {
    func move() {
        print("動物が移動します。")
    }
}

class CanFly {
    func fly() {
        print("空を飛びます。")
    }
}

class FlyingDog: Animal, CanFly {
    // AnimalとCanFlyの特性を組み合わせたクラス
}

let dog = FlyingDog()
dog.move()  // 動物が移動します。
dog.fly()   // 空を飛びます。

このように、Swiftの継承を使うことで、異なるクラスの特性を組み合わせた新しいクラスを効率よく作成することができます。

○サンプルコード7:抽象クラスと具象クラス

SwiftにはJavaやC#のような抽象クラスの概念は存在しませんが、プロトコルを利用することで同様の効果を実現することができます。

抽象クラスとは、インスタンスを持つことができないクラスで、一つ以上の未実装のメソッドやプロパティを持つことが特徴です。

ここでは、動物の抽象クラスと、その具象クラスである犬のクラスを表すサンプルコードを紹介します。

protocol AbstractAnimal {
    func sound() -> String
}

class Dog: AbstractAnimal {
    func sound() -> String {
        return "ワンワン"
    }
}

let dog = Dog()
print(dog.sound())  // ワンワン

プロトコルAbstractAnimalが抽象クラスの役割を果たし、具象クラスDogではそのメソッドを実装しています。

このように、Swiftではプロトコルを活用することで抽象クラスのような設計を行うことができます。

○サンプルコード8:継承とプロトコルの組み合わせ

Swiftのプロトコルは、特定のメソッドやプロパティの契約を定義する機能を持ちます。

継承とプロトコルを組み合わせることで、一つのクラスが複数のプロトコルの契約を守ることを保証しつつ、親クラスからの特性も継承することが可能です。

下記のサンプルコードでは、動く飛ぶという2つのプロトコルを定義しています。

そして、Birdクラスはこれらのプロトコルを採用し、さらにAnimalクラスからの特性も継承しています。

class Animal {
    func breathe() {
        print("呼吸する")
    }
}

protocol Moveable {
    func move()
}

protocol Flyable {
    func fly()
}

class Bird: Animal, Moveable, Flyable {
    func move() {
        print("歩く")
    }

    func fly() {
        print("空を飛ぶ")
    }
}

let sparrow = Bird()
sparrow.breathe()  // 呼吸する
sparrow.move()     // 歩く
sparrow.fly()      // 空を飛ぶ

このコードを実行すると、sparrowオブジェクトはAnimalクラスのbreatheメソッドの機能と、MoveableFlyableプロトコルのメソッドの機能を利用することができます。

○サンプルコード9:拡張(Extension)と継承

Swiftでは、拡張(Extension)を使用することで、既存のクラスや構造体、列挙型に新しい機能を追加することができます。

これにより、既存のコードを変更することなく、新しい機能を付加することが可能となります。

下記のサンプルコードでは、Animalクラスに拡張を使用して、新しいメソッドを追加しています。

class Animal {
    func eat() {
        print("食事する")
    }
}

extension Animal {
    func sleep() {
        print("眠る")
    }
}

class Dog: Animal {
    func bark() {
        print("ワンワン")
    }
}

let dog = Dog()
dog.eat()   // 食事する
dog.sleep() // 眠る
dog.bark()  // ワンワン

このコードにより、DogクラスのオブジェクトはAnimalクラスから継承したeatメソッド、拡張によって追加されたsleepメソッド、そして自身のbarkメソッドを利用することができます。

○サンプルコード10:ジェネリクスを用いた継承

Swiftのジェネリクスは、型をパラメータとして持つことができる機能を指します。

これにより、一つのクラスや関数でさまざまな型を扱うことができます。

継承とジェネリクスを組み合わせることで、さらに柔軟なコードの実装が可能となります。

ここでは、ジェネリクスを使用して継承を行ったサンプルコードを紹介します。

class Box<T> {
    var item: T

    init(item: T) {
        self.item = item
    }
}

class NumberBox: Box<Int> {
    func increment() {
        item += 1
    }
}

let box = NumberBox(item: 5)
print(box.item)  // 5
box.increment()
print(box.item)  // 6

Boxクラスはジェネリクスを使用しており、任意の型Tのアイテムを保持することができます。

NumberBoxクラスはこのBoxクラスを継承し、特にInt型に特化した機能を持つクラスとして実装されています。

このコードを実行すると、box.itemは初めに5と表示され、incrementメソッドを実行した後には6と表示されます。

●継承時の注意点と対処法

Swiftで継承を利用する際、意識しなければならない注意点や問題がいくつか存在します。

これらの点を知っておくことで、より安全で効果的なプログラムを作成するための参考となります。

○循環参照とは?

循環参照は、オブジェクトが互いに強い参照を持っている状態を指し、これによってメモリリークが発生することがあります。

Swiftでは、ARC(Automatic Reference Counting)がメモリ管理を行っていますが、循環参照が発生するとARCの機能だけではメモリ解放がうまく行えません。

class Teacher {
    var student: Student?
    deinit {
        print("Teacherインスタンス解放")
    }
}

class Student {
    var teacher: Teacher?
    deinit {
        print("Studentインスタンス解放")
    }
}

var teacher1: Teacher? = Teacher()
var student1: Student? = Student()

teacher1?.student = student1
student1?.teacher = teacher1

teacher1 = nil
student1 = nil

このコードでは、TeacherStudentの二つのクラスがあり、それぞれのインスタンスが相手のインスタンスを参照しています。

循環参照のため、nilを代入してもdeinitが呼び出されず、メモリリークが発生します。

○強参照、弱参照、アンオウンド参照の違いと対処法

循環参照の問題を解決するために、Swiftには強参照、弱参照、アンオウンド参照という3つの参照の種類が存在します。

  • 強参照:デフォルトの参照方法。オブジェクトが解放されないように保持します。
  • 弱参照:weakキーワードを使用。参照カウントを増やさず、オブジェクトが解放された時点で自動的にnilになります。
  • アンオウンド参照:unownedキーワードを使用。弱参照と同様に参照カウントを増やさないが、オブジェクトが解放された後もnilにならず、アクセスするとランタイムクラッシュが発生する。

循環参照を避けるためのサンプルコードを見てみましょう。

class Teacher {
    var student: Student?
    deinit {
        print("Teacherインスタンス解放")
    }
}

class Student {
    weak var teacher: Teacher?
    deinit {
        print("Studentインスタンス解放")
    }
}

var teacher2: Teacher? = Teacher()
var student2: Student? = Student()

teacher2?.student = student2
student2?.teacher = teacher2

teacher2 = nil
student2 = nil

このコードでは、Studentクラスのteacherプロパティにweakキーワードを使用しています。

これにより、teacher2student2nilに設定したとき、両方のインスタンスが正しく解放されることが確認できます。

●継承のカスタマイズ方法

Swiftの継承は、オブジェクト指向プログラミングの強力なツールの1つとして、プログラムの再利用や拡張を容易にします。

しかし、場合によっては、継承の挙動をカスタマイズする必要が出てきます。

ここでは、そのようなカスタマイズ方法について詳しく解説します。

○finalキーワードの使用方法

Swiftにはfinalというキーワードがあります。

これは、クラス、メソッド、プロパティなどがオーバーライドされることを防ぐためのものです。

特定の振る舞いを保護するため、または継承の階層を明確にするためにfinalを使用することが推奨されます。

ここではfinalキーワードをクラスとメソッドに使用したサンプルコードを紹介します。

final class FixedClass {
    var value = 100

    func displayValue() {
        print("値は\(value)です。")
    }
}

このコードではFixedClassfinalと宣言されているため、他のクラスがこのクラスを継承することはできません。

したがって、FixedClassの振る舞いが変更されるリスクを排除することができます。

○継承の制限と拡張

継承は非常に強力なツールですが、全てのクラスが継承の対象となるわけではありません。

Swiftでは、特定のクラスやメソッドを継承の対象から除外したい場合、制限をかけることができます。

逆に、継承を利用して新しい機能を追加する方法もあります。

例えば、継承を使ってクラスの機能を拡張する場合、次のようなサンプルコードを考えることができます。

class Vehicle {
    var speed: Int = 0

    func accelerate() {
        speed += 10
    }
}

class RaceCar: Vehicle {
    var turboBoost = false

    override func accelerate() {
        if turboBoost {
            speed += 50
        } else {
            super.accelerate()
        }
    }
}

let car = RaceCar()
car.turboBoost = true
car.accelerate()

// 結果出力
print("レーシングカーの速度は\(car.speed)km/hです。")

このコードを実行すると、レーシングカーの速度は50km/hです。と出力されます。

RaceCarクラスはVehicleクラスを継承しており、accelerateメソッドをオーバーライドして、ターボブーストが有効の場合は速度を迅速に上げる機能を追加しています。

まとめ

Swiftの継承は、オブジェクト指向プログラミングにおける中心的な概念の1つです。

この記事を通じて、継承の基本から応用、さらにはカスタマイズ方法まで、幅広い側面を学ぶことができたかと思います。

継承を利用することで、既存のクラスの機能を再利用し、新しいクラスを効率的に設計することが可能になります。

また、オーバーライドを駆使することで、親クラスの振る舞いを子クラスで特化した形に変更することもできます。

しかし、無計画に継承を用いると、コードの複雑性が増す恐れもあります。

特に、finalキーワードを活用して、特定のクラスやメソッドのオーバーライドを防止することは、Swiftの継承を安全に活用する上での重要なテクニックとなります。

Swiftの継承に関する学びを深めることで、より品質の高いアプリケーションの開発が期待できます。

プログラムの再利用や拡張を考える際に、本記事の内容を思い出して、適切な継承の手法を選択してください。